Remove dev servers (#19638)

Mikayla Maki , Conrad , and Conrad Irwin created

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>

Change summary

Cargo.lock                                            |   45 
Cargo.toml                                            |    4 
crates/assistant/src/assistant_panel.rs               |    2 
crates/call/src/room.rs                               |   27 
crates/cli/src/cli.rs                                 |    1 
crates/cli/src/main.rs                                |    7 
crates/client/src/client.rs                           |   58 
crates/client/src/test.rs                             |    4 
crates/client/src/user.rs                             |    3 
crates/collab/Cargo.toml                              |    2 
crates/collab/src/auth.rs                             |   54 
crates/collab/src/db.rs                               |    1 
crates/collab/src/db/ids.rs                           |    8 
crates/collab/src/db/queries.rs                       |    2 
crates/collab/src/db/queries/dev_server_projects.rs   |  364 ----
crates/collab/src/db/queries/dev_servers.rs           |  221 --
crates/collab/src/db/queries/projects.rs              |  170 -
crates/collab/src/db/queries/rooms.rs                 |   54 
crates/collab/src/db/tables.rs                        |    2 
crates/collab/src/db/tables/dev_server.rs             |   39 
crates/collab/src/db/tables/dev_server_project.rs     |   59 
crates/collab/src/db/tables/project.rs                |   15 
crates/collab/src/db/tests/db_tests.rs                |    6 
crates/collab/src/rpc.rs                              | 1092 +-----------
crates/collab/src/rpc/connection_pool.rs              |  127 -
crates/collab/src/tests.rs                            |    1 
crates/collab/src/tests/dev_server_tests.rs           |  643 -------
crates/collab/src/tests/test_server.rs                |  135 -
crates/dev_server_projects/Cargo.toml                 |   23 
crates/dev_server_projects/LICENSE-GPL                |    1 
crates/dev_server_projects/src/dev_server_projects.rs |  248 --
crates/headless/Cargo.toml                            |   37 
crates/headless/LICENSE-GPL                           |    1 
crates/headless/src/headless.rs                       |  397 ----
crates/project/Cargo.toml                             |    1 
crates/project/src/project.rs                         |  110 -
crates/project/src/terminals.rs                       |   28 
crates/project/src/worktree_store.rs                  |   63 
crates/project_panel/src/project_panel.rs             |   13 
crates/proto/proto/zed.proto                          |  141 -
crates/proto/src/proto.rs                             |   30 
crates/recent_projects/Cargo.toml                     |    4 
crates/recent_projects/src/disconnected_overlay.rs    |   74 
crates/recent_projects/src/recent_projects.rs         |  248 --
crates/recent_projects/src/remote_servers.rs          |  158 -
crates/title_bar/Cargo.toml                           |    1 
crates/title_bar/src/collab.rs                        |    5 
crates/title_bar/src/title_bar.rs                     |   35 
crates/workspace/Cargo.toml                           |    1 
crates/workspace/src/persistence.rs                   |  171 -
crates/workspace/src/persistence/model.rs             |   48 
crates/workspace/src/workspace.rs                     |  105 -
crates/zed/Cargo.toml                                 |    2 
crates/zed/src/main.rs                                |  113 -
crates/zed/src/zed/open_listener.rs                   |   36 
55 files changed, 345 insertions(+), 4,895 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -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",

Cargo.toml 🔗

@@ -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" }

crates/assistant/src/assistant_panel.rs 🔗

@@ -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));

crates/call/src/room.rs 🔗

@@ -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?;

crates/cli/src/cli.rs 🔗

@@ -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>>,
     },
 }

crates/cli/src/main.rs 🔗

@@ -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,
             })?;
 

crates/client/src/client.rs 🔗

@@ -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(),
                 })

crates/client/src/test.rs 🔗

@@ -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(),
                             })

crates/client/src/user.rs 🔗

@@ -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,
 )]

crates/collab/Cargo.toml 🔗

@@ -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"] }

crates/collab/src/auth.rs 🔗

@@ -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;

crates/collab/src/db.rs 🔗

@@ -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 {

crates/collab/src/db/ids.rs 🔗

@@ -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 {

crates/collab/src/db/queries.rs 🔗

@@ -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;

crates/collab/src/db/queries/dev_server_projects.rs 🔗

@@ -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
-    }
-}

crates/collab/src/db/queries/dev_servers.rs 🔗

@@ -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
-    }
-}

crates/collab/src/db/queries/projects.rs 🔗

@@ -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()?;

crates/collab/src/db/queries/rooms.rs 🔗

@@ -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);
-                        }
-                    }
-                }
             }
         }
 

crates/collab/src/db/tables.rs 🔗

@@ -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;

crates/collab/src/db/tables/dev_server.rs 🔗

@@ -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(),
-        }
-    }
-}

crates/collab/src/db/tables/dev_server_project.rs 🔗

@@ -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
-    }
-}

crates/collab/src/db/tables/project.rs 🔗

@@ -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 {}

crates/collab/src/db/tests/db_tests.rs 🔗

@@ -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);

crates/collab/src/rpc.rs 🔗

@@ -5,11 +5,10 @@ use crate::llm::LlmTokenClaims;
 use crate::{
     auth,
     db::{
-        self, dev_server, BufferId, Capability, Channel, ChannelId, ChannelRole, ChannelsForUser,
-        CreatedChannelMessage, Database, DevServerId, DevServerProjectId, InviteMemberResult,
-        MembershipUpdated, MessageId, NotificationId, PrincipalId, Project, ProjectId,
-        RejoinedProject, RemoveChannelMemberResult, ReplicaId, RespondToChannelInvite, RoomId,
-        ServerId, UpdatedChannelMessage, User, UserId,
+        self, BufferId, Capability, Channel, ChannelId, ChannelRole, ChannelsForUser,
+        CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId,
+        NotificationId, Project, ProjectId, RejoinedProject, RemoveChannelMemberResult, ReplicaId,
+        RespondToChannelInvite, RoomId, ServerId, UpdatedChannelMessage, User, UserId,
     },
     executor::Executor,
     AppState, Config, Error, RateLimit, Result,
@@ -42,10 +41,8 @@ use sha2::Digest;
 use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
 
 use futures::{
-    channel::oneshot,
-    future::{self, BoxFuture},
-    stream::FuturesUnordered,
-    FutureExt, SinkExt, StreamExt, TryStreamExt,
+    channel::oneshot, future::BoxFuture, stream::FuturesUnordered, FutureExt, SinkExt, StreamExt,
+    TryStreamExt,
 };
 use prometheus::{register_int_gauge, IntGauge};
 use rpc::{
@@ -109,7 +106,6 @@ impl<R: RequestMessage> Response<R> {
 pub enum Principal {
     User(User),
     Impersonated { user: User, admin: User },
-    DevServer(dev_server::Model),
 }
 
 impl Principal {
@@ -124,9 +120,6 @@ impl Principal {
                 span.record("login", &user.github_login);
                 span.record("impersonator", &admin.github_login);
             }
-            Principal::DevServer(dev_server) => {
-                span.record("dev_server_id", dev_server.id.0);
-            }
         }
     }
 }
@@ -167,27 +160,10 @@ impl Session {
         }
     }
 
-    fn for_user(self) -> Option<UserSession> {
-        UserSession::new(self)
-    }
-
-    fn for_dev_server(self) -> Option<DevServerSession> {
-        DevServerSession::new(self)
-    }
-
-    fn user_id(&self) -> Option<UserId> {
-        match &self.principal {
-            Principal::User(user) => Some(user.id),
-            Principal::Impersonated { user, .. } => Some(user.id),
-            Principal::DevServer(_) => None,
-        }
-    }
-
     fn is_staff(&self) -> bool {
         match &self.principal {
             Principal::User(user) => user.admin,
             Principal::Impersonated { .. } => true,
-            Principal::DevServer(_) => false,
         }
     }
 
@@ -199,9 +175,7 @@ impl Session {
             return Ok(true);
         }
 
-        let Some(user_id) = self.user_id() else {
-            return Ok(false);
-        };
+        let user_id = self.user_id();
 
         Ok(db.has_active_billing_subscription(user_id).await?)
     }
@@ -217,18 +191,17 @@ impl Session {
         }
     }
 
-    fn dev_server_id(&self) -> Option<DevServerId> {
+    fn user_id(&self) -> UserId {
         match &self.principal {
-            Principal::User(_) | Principal::Impersonated { .. } => None,
-            Principal::DevServer(dev_server) => Some(dev_server.id),
+            Principal::User(user) => user.id,
+            Principal::Impersonated { user, .. } => user.id,
         }
     }
 
-    fn principal_id(&self) -> PrincipalId {
+    pub fn email(&self) -> Option<String> {
         match &self.principal {
-            Principal::User(user) => PrincipalId::UserId(user.id),
-            Principal::Impersonated { user, .. } => PrincipalId::UserId(user.id),
-            Principal::DevServer(dev_server) => PrincipalId::DevServerId(dev_server.id),
+            Principal::User(user) => user.email_address.clone(),
+            Principal::Impersonated { user, .. } => user.email_address.clone(),
         }
     }
 }
@@ -244,143 +217,11 @@ impl Debug for Session {
                 result.field("user", &user.github_login);
                 result.field("impersonator", &admin.github_login);
             }
-            Principal::DevServer(dev_server) => {
-                result.field("dev_server", &dev_server.id);
-            }
         }
         result.field("connection_id", &self.connection_id).finish()
     }
 }
 
-struct UserSession(Session);
-
-impl UserSession {
-    pub fn new(s: Session) -> Option<Self> {
-        s.user_id().map(|_| UserSession(s))
-    }
-    pub fn user_id(&self) -> UserId {
-        self.0.user_id().unwrap()
-    }
-
-    pub fn email(&self) -> Option<String> {
-        match &self.0.principal {
-            Principal::User(user) => user.email_address.clone(),
-            Principal::Impersonated { user, .. } => user.email_address.clone(),
-            Principal::DevServer(..) => None,
-        }
-    }
-}
-
-impl Deref for UserSession {
-    type Target = Session;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-impl DerefMut for UserSession {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-struct DevServerSession(Session);
-
-impl DevServerSession {
-    pub fn new(s: Session) -> Option<Self> {
-        s.dev_server_id().map(|_| DevServerSession(s))
-    }
-    pub fn dev_server_id(&self) -> DevServerId {
-        self.0.dev_server_id().unwrap()
-    }
-
-    fn dev_server(&self) -> &dev_server::Model {
-        match &self.0.principal {
-            Principal::DevServer(dev_server) => dev_server,
-            _ => unreachable!(),
-        }
-    }
-}
-
-impl Deref for DevServerSession {
-    type Target = Session;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-impl DerefMut for DevServerSession {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-fn user_handler<M: RequestMessage, Fut>(
-    handler: impl 'static + Send + Sync + Fn(M, Response<M>, UserSession) -> Fut,
-) -> impl 'static + Send + Sync + Fn(M, Response<M>, Session) -> BoxFuture<'static, Result<()>>
-where
-    Fut: Send + Future<Output = Result<()>>,
-{
-    let handler = Arc::new(handler);
-    move |message, response, session| {
-        let handler = handler.clone();
-        Box::pin(async move {
-            if let Some(user_session) = session.for_user() {
-                Ok(handler(message, response, user_session).await?)
-            } else {
-                Err(Error::Internal(anyhow!(
-                    "must be a user to call {}",
-                    M::NAME
-                )))
-            }
-        })
-    }
-}
-
-fn dev_server_handler<M: RequestMessage, Fut>(
-    handler: impl 'static + Send + Sync + Fn(M, Response<M>, DevServerSession) -> Fut,
-) -> impl 'static + Send + Sync + Fn(M, Response<M>, Session) -> BoxFuture<'static, Result<()>>
-where
-    Fut: Send + Future<Output = Result<()>>,
-{
-    let handler = Arc::new(handler);
-    move |message, response, session| {
-        let handler = handler.clone();
-        Box::pin(async move {
-            if let Some(dev_server_session) = session.for_dev_server() {
-                Ok(handler(message, response, dev_server_session).await?)
-            } else {
-                Err(Error::Internal(anyhow!(
-                    "must be a dev server to call {}",
-                    M::NAME
-                )))
-            }
-        })
-    }
-}
-
-fn user_message_handler<M: EnvelopedMessage, InnertRetFut>(
-    handler: impl 'static + Send + Sync + Fn(M, UserSession) -> InnertRetFut,
-) -> impl 'static + Send + Sync + Fn(M, Session) -> BoxFuture<'static, Result<()>>
-where
-    InnertRetFut: Send + Future<Output = Result<()>>,
-{
-    let handler = Arc::new(handler);
-    move |message, session| {
-        let handler = handler.clone();
-        Box::pin(async move {
-            if let Some(user_session) = session.for_user() {
-                Ok(handler(message, user_session).await?)
-            } else {
-                Err(Error::Internal(anyhow!(
-                    "must be a user to call {}",
-                    M::NAME
-                )))
-            }
-        })
-    }
-}
-
 struct DbHandle(Arc<Database>);
 
 impl Deref for DbHandle {
@@ -434,141 +275,64 @@ impl Server {
 
         server
             .add_request_handler(ping)
-            .add_request_handler(user_handler(create_room))
-            .add_request_handler(user_handler(join_room))
-            .add_request_handler(user_handler(rejoin_room))
-            .add_request_handler(user_handler(leave_room))
-            .add_request_handler(user_handler(set_room_participant_role))
-            .add_request_handler(user_handler(call))
-            .add_request_handler(user_handler(cancel_call))
-            .add_message_handler(user_message_handler(decline_call))
-            .add_request_handler(user_handler(update_participant_location))
-            .add_request_handler(user_handler(share_project))
+            .add_request_handler(create_room)
+            .add_request_handler(join_room)
+            .add_request_handler(rejoin_room)
+            .add_request_handler(leave_room)
+            .add_request_handler(set_room_participant_role)
+            .add_request_handler(call)
+            .add_request_handler(cancel_call)
+            .add_message_handler(decline_call)
+            .add_request_handler(update_participant_location)
+            .add_request_handler(share_project)
             .add_message_handler(unshare_project)
-            .add_request_handler(user_handler(join_project))
-            .add_request_handler(user_handler(join_hosted_project))
-            .add_request_handler(user_handler(rejoin_dev_server_projects))
-            .add_request_handler(user_handler(create_dev_server_project))
-            .add_request_handler(user_handler(update_dev_server_project))
-            .add_request_handler(user_handler(delete_dev_server_project))
-            .add_request_handler(user_handler(create_dev_server))
-            .add_request_handler(user_handler(regenerate_dev_server_token))
-            .add_request_handler(user_handler(rename_dev_server))
-            .add_request_handler(user_handler(delete_dev_server))
-            .add_request_handler(user_handler(list_remote_directory))
-            .add_request_handler(dev_server_handler(share_dev_server_project))
-            .add_request_handler(dev_server_handler(shutdown_dev_server))
-            .add_request_handler(dev_server_handler(reconnect_dev_server))
-            .add_message_handler(user_message_handler(leave_project))
+            .add_request_handler(join_project)
+            .add_request_handler(join_hosted_project)
+            .add_message_handler(leave_project)
             .add_request_handler(update_project)
             .add_request_handler(update_worktree)
             .add_message_handler(start_language_server)
             .add_message_handler(update_language_server)
             .add_message_handler(update_diagnostic_summary)
             .add_message_handler(update_worktree_settings)
-            .add_request_handler(user_handler(
-                forward_project_request_for_owner::<proto::TaskContextForLocation>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::GetHover>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::GetDefinition>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::GetTypeDefinition>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::GetReferences>,
-            ))
-            .add_request_handler(user_handler(forward_find_search_candidates_request))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::GetDocumentHighlights>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::GetProjectSymbols>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::OpenBufferForSymbol>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::OpenBufferById>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::SynchronizeBuffers>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::InlayHints>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::ResolveInlayHint>,
-            ))
-            .add_request_handler(user_handler(
-                forward_read_only_project_request::<proto::OpenBufferByPath>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::GetCompletions>,
-            ))
-            .add_request_handler(user_handler(
+            .add_request_handler(forward_read_only_project_request::<proto::GetHover>)
+            .add_request_handler(forward_read_only_project_request::<proto::GetDefinition>)
+            .add_request_handler(forward_read_only_project_request::<proto::GetTypeDefinition>)
+            .add_request_handler(forward_read_only_project_request::<proto::GetReferences>)
+            .add_request_handler(forward_find_search_candidates_request)
+            .add_request_handler(forward_read_only_project_request::<proto::GetDocumentHighlights>)
+            .add_request_handler(forward_read_only_project_request::<proto::GetProjectSymbols>)
+            .add_request_handler(forward_read_only_project_request::<proto::OpenBufferForSymbol>)
+            .add_request_handler(forward_read_only_project_request::<proto::OpenBufferById>)
+            .add_request_handler(forward_read_only_project_request::<proto::SynchronizeBuffers>)
+            .add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
+            .add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
+            .add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
+            .add_request_handler(forward_mutating_project_request::<proto::GetCompletions>)
+            .add_request_handler(
                 forward_mutating_project_request::<proto::ApplyCompletionAdditionalEdits>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::OpenNewBuffer>,
-            ))
-            .add_request_handler(user_handler(
+            )
+            .add_request_handler(forward_mutating_project_request::<proto::OpenNewBuffer>)
+            .add_request_handler(
                 forward_mutating_project_request::<proto::ResolveCompletionDocumentation>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::GetCodeActions>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::ApplyCodeAction>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::PrepareRename>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::PerformRename>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::ReloadBuffers>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::FormatBuffers>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::CreateProjectEntry>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::RenameProjectEntry>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::CopyProjectEntry>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::DeleteProjectEntry>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::ExpandProjectEntry>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::OnTypeFormatting>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::SaveBuffer>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::BlameBuffer>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::MultiLspQuery>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::RestartLanguageServers>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::LinkedEditingRange>,
-            ))
+            )
+            .add_request_handler(forward_mutating_project_request::<proto::GetCodeActions>)
+            .add_request_handler(forward_mutating_project_request::<proto::ApplyCodeAction>)
+            .add_request_handler(forward_mutating_project_request::<proto::PrepareRename>)
+            .add_request_handler(forward_mutating_project_request::<proto::PerformRename>)
+            .add_request_handler(forward_mutating_project_request::<proto::ReloadBuffers>)
+            .add_request_handler(forward_mutating_project_request::<proto::FormatBuffers>)
+            .add_request_handler(forward_mutating_project_request::<proto::CreateProjectEntry>)
+            .add_request_handler(forward_mutating_project_request::<proto::RenameProjectEntry>)
+            .add_request_handler(forward_mutating_project_request::<proto::CopyProjectEntry>)
+            .add_request_handler(forward_mutating_project_request::<proto::DeleteProjectEntry>)
+            .add_request_handler(forward_mutating_project_request::<proto::ExpandProjectEntry>)
+            .add_request_handler(forward_mutating_project_request::<proto::OnTypeFormatting>)
+            .add_request_handler(forward_mutating_project_request::<proto::SaveBuffer>)
+            .add_request_handler(forward_mutating_project_request::<proto::BlameBuffer>)
+            .add_request_handler(forward_mutating_project_request::<proto::MultiLspQuery>)
+            .add_request_handler(forward_mutating_project_request::<proto::RestartLanguageServers>)
+            .add_request_handler(forward_mutating_project_request::<proto::LinkedEditingRange>)
             .add_message_handler(create_buffer_for_peer)
             .add_request_handler(update_buffer)
             .add_message_handler(broadcast_project_message_from_host::<proto::RefreshInlayHints>)
@@ -577,53 +341,47 @@ impl Server {
             .add_message_handler(broadcast_project_message_from_host::<proto::BufferSaved>)
             .add_message_handler(broadcast_project_message_from_host::<proto::UpdateDiffBase>)
             .add_request_handler(get_users)
-            .add_request_handler(user_handler(fuzzy_search_users))
-            .add_request_handler(user_handler(request_contact))
-            .add_request_handler(user_handler(remove_contact))
-            .add_request_handler(user_handler(respond_to_contact_request))
+            .add_request_handler(fuzzy_search_users)
+            .add_request_handler(request_contact)
+            .add_request_handler(remove_contact)
+            .add_request_handler(respond_to_contact_request)
             .add_message_handler(subscribe_to_channels)
-            .add_request_handler(user_handler(create_channel))
-            .add_request_handler(user_handler(delete_channel))
-            .add_request_handler(user_handler(invite_channel_member))
-            .add_request_handler(user_handler(remove_channel_member))
-            .add_request_handler(user_handler(set_channel_member_role))
-            .add_request_handler(user_handler(set_channel_visibility))
-            .add_request_handler(user_handler(rename_channel))
-            .add_request_handler(user_handler(join_channel_buffer))
-            .add_request_handler(user_handler(leave_channel_buffer))
-            .add_message_handler(user_message_handler(update_channel_buffer))
-            .add_request_handler(user_handler(rejoin_channel_buffers))
-            .add_request_handler(user_handler(get_channel_members))
-            .add_request_handler(user_handler(respond_to_channel_invite))
-            .add_request_handler(user_handler(join_channel))
-            .add_request_handler(user_handler(join_channel_chat))
-            .add_message_handler(user_message_handler(leave_channel_chat))
-            .add_request_handler(user_handler(send_channel_message))
-            .add_request_handler(user_handler(remove_channel_message))
-            .add_request_handler(user_handler(update_channel_message))
-            .add_request_handler(user_handler(get_channel_messages))
-            .add_request_handler(user_handler(get_channel_messages_by_id))
-            .add_request_handler(user_handler(get_notifications))
-            .add_request_handler(user_handler(mark_notification_as_read))
-            .add_request_handler(user_handler(move_channel))
-            .add_request_handler(user_handler(follow))
-            .add_message_handler(user_message_handler(unfollow))
-            .add_message_handler(user_message_handler(update_followers))
-            .add_request_handler(user_handler(get_private_user_info))
-            .add_request_handler(user_handler(get_llm_api_token))
-            .add_request_handler(user_handler(accept_terms_of_service))
-            .add_message_handler(user_message_handler(acknowledge_channel_message))
-            .add_message_handler(user_message_handler(acknowledge_buffer_version))
-            .add_request_handler(user_handler(get_supermaven_api_key))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::OpenContext>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::CreateContext>,
-            ))
-            .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::SynchronizeContexts>,
-            ))
+            .add_request_handler(create_channel)
+            .add_request_handler(delete_channel)
+            .add_request_handler(invite_channel_member)
+            .add_request_handler(remove_channel_member)
+            .add_request_handler(set_channel_member_role)
+            .add_request_handler(set_channel_visibility)
+            .add_request_handler(rename_channel)
+            .add_request_handler(join_channel_buffer)
+            .add_request_handler(leave_channel_buffer)
+            .add_message_handler(update_channel_buffer)
+            .add_request_handler(rejoin_channel_buffers)
+            .add_request_handler(get_channel_members)
+            .add_request_handler(respond_to_channel_invite)
+            .add_request_handler(join_channel)
+            .add_request_handler(join_channel_chat)
+            .add_message_handler(leave_channel_chat)
+            .add_request_handler(send_channel_message)
+            .add_request_handler(remove_channel_message)
+            .add_request_handler(update_channel_message)
+            .add_request_handler(get_channel_messages)
+            .add_request_handler(get_channel_messages_by_id)
+            .add_request_handler(get_notifications)
+            .add_request_handler(mark_notification_as_read)
+            .add_request_handler(move_channel)
+            .add_request_handler(follow)
+            .add_message_handler(unfollow)
+            .add_message_handler(update_followers)
+            .add_request_handler(get_private_user_info)
+            .add_request_handler(get_llm_api_token)
+            .add_request_handler(accept_terms_of_service)
+            .add_message_handler(acknowledge_channel_message)
+            .add_message_handler(acknowledge_buffer_version)
+            .add_request_handler(get_supermaven_api_key)
+            .add_request_handler(forward_mutating_project_request::<proto::OpenContext>)
+            .add_request_handler(forward_mutating_project_request::<proto::CreateContext>)
+            .add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>)
             .add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
             .add_message_handler(update_context)
             .add_request_handler({
@@ -636,21 +394,17 @@ impl Server {
                     }
                 }
             })
-            .add_request_handler({
-                user_handler(move |request, response, session| {
-                    get_cached_embeddings(request, response, session)
-                })
-            })
+            .add_request_handler(get_cached_embeddings)
             .add_request_handler({
                 let app_state = app_state.clone();
-                user_handler(move |request, response, session| {
+                move |request, response, session| {
                     compute_embeddings(
                         request,
                         response,
                         session,
                         app_state.config.openai_api_key.clone(),
                     )
-                })
+                }
             });
 
         Arc::new(server)
@@ -936,7 +690,6 @@ impl Server {
             user_id=field::Empty,
             login=field::Empty,
             impersonator=field::Empty,
-            dev_server_id=field::Empty,
             geoip_country_code=field::Empty
         );
         principal.update_span(&span);
@@ -1031,7 +784,6 @@ impl Server {
                                 user_id=field::Empty,
                                 login=field::Empty,
                                 impersonator=field::Empty,
-                                dev_server_id=field::Empty
                             );
                             principal.update_span(&span);
                             let span_enter = span.enter();
@@ -1100,11 +852,7 @@ impl Server {
 
                 update_user_plan(user.id, session).await?;
 
-                let (contacts, dev_server_projects) = future::try_join(
-                    self.app_state.db.get_contacts(user.id),
-                    self.app_state.db.dev_server_projects_update(user.id),
-                )
-                .await?;
+                let contacts = self.app_state.db.get_contacts(user.id).await?;
 
                 {
                     let mut pool = self.connection_pool.lock();
@@ -1119,8 +867,6 @@ impl Server {
                     subscribe_user_to_channels(user.id, session).await?;
                 }
 
-                send_dev_server_projects_update(user.id, dev_server_projects, session).await;
-
                 if let Some(incoming_call) =
                     self.app_state.db.incoming_call_for_user(user.id).await?
                 {
@@ -1129,39 +875,6 @@ impl Server {
 
                 update_user_contacts(user.id, session).await?;
             }
-            Principal::DevServer(dev_server) => {
-                {
-                    let mut pool = self.connection_pool.lock();
-                    if let Some(stale_connection_id) = pool.dev_server_connection_id(dev_server.id)
-                    {
-                        self.peer.send(
-                            stale_connection_id,
-                            proto::ShutdownDevServer {
-                                reason: Some(
-                                    "another dev server connected with the same token".to_string(),
-                                ),
-                            },
-                        )?;
-                        pool.remove_connection(stale_connection_id)?;
-                    };
-                    pool.add_dev_server(connection_id, dev_server.id, zed_version);
-                }
-
-                let projects = self
-                    .app_state
-                    .db
-                    .get_projects_for_dev_server(dev_server.id)
-                    .await?;
-                self.peer
-                    .send(connection_id, proto::DevServerInstructions { projects })?;
-
-                let status = self
-                    .app_state
-                    .db
-                    .dev_server_projects_update(dev_server.user_id)
-                    .await?;
-                send_dev_server_projects_update(dev_server.user_id, status, session).await;
-            }
         }
 
         Ok(())
@@ -1452,33 +1165,25 @@ async fn connection_lost(
 
     futures::select_biased! {
         _ = executor.sleep(RECONNECT_TIMEOUT).fuse() => {
-            match &session.principal {
-                Principal::User(_) | Principal::Impersonated{ user: _, admin:_ } => {
-                    let session = session.for_user().unwrap();
-
-                    log::info!("connection lost, removing all resources for user:{}, connection:{:?}", session.user_id(), session.connection_id);
-                    leave_room_for_session(&session, session.connection_id).await.trace_err();
-                    leave_channel_buffers_for_session(&session)
-                        .await
-                        .trace_err();
-
-                    if !session
-                        .connection_pool()
-                        .await
-                        .is_user_online(session.user_id())
-                    {
-                        let db = session.db().await;
-                        if let Some(room) = db.decline_call(None, session.user_id()).await.trace_err().flatten() {
-                            room_updated(&room, &session.peer);
-                        }
-                    }
 
-                    update_user_contacts(session.user_id(), &session).await?;
-                },
-            Principal::DevServer(_) => {
-                lost_dev_server_connection(&session.for_dev_server().unwrap()).await?;
-            },
-        }
+            log::info!("connection lost, removing all resources for user:{}, connection:{:?}", session.user_id(), session.connection_id);
+            leave_room_for_session(&session, session.connection_id).await.trace_err();
+            leave_channel_buffers_for_session(&session)
+                .await
+                .trace_err();
+
+            if !session
+                .connection_pool()
+                .await
+                .is_user_online(session.user_id())
+            {
+                let db = session.db().await;
+                if let Some(room) = db.decline_call(None, session.user_id()).await.trace_err().flatten() {
+                    room_updated(&room, &session.peer);
+                }
+            }
+
+            update_user_contacts(session.user_id(), &session).await?;
         },
         _ = teardown.changed().fuse() => {}
     }
@@ -1496,7 +1201,7 @@ async fn ping(_: proto::Ping, response: Response<proto::Ping>, _session: Session
 async fn create_room(
     _request: proto::CreateRoom,
     response: Response<proto::CreateRoom>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let live_kit_room = nanoid::nanoid!(30);
 
@@ -1536,7 +1241,7 @@ async fn create_room(
 async fn join_room(
     request: proto::JoinRoom,
     response: Response<proto::JoinRoom>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let room_id = RoomId::from_proto(request.id);
 
@@ -1603,7 +1308,7 @@ async fn join_room(
 async fn rejoin_room(
     request: proto::RejoinRoom,
     response: Response<proto::RejoinRoom>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let room;
     let channel;
@@ -1693,7 +1398,7 @@ async fn rejoin_room(
 
 fn notify_rejoined_projects(
     rejoined_projects: &mut Vec<RejoinedProject>,
-    session: &UserSession,
+    session: &Session,
 ) -> Result<()> {
     for project in rejoined_projects.iter() {
         for collaborator in &project.collaborators {
@@ -1778,7 +1483,7 @@ fn notify_rejoined_projects(
 async fn leave_room(
     _: proto::LeaveRoom,
     response: Response<proto::LeaveRoom>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     leave_room_for_session(&session, session.connection_id).await?;
     response.send(proto::Ack {})?;
@@ -1789,7 +1494,7 @@ async fn leave_room(
 async fn set_room_participant_role(
     request: proto::SetRoomParticipantRole,
     response: Response<proto::SetRoomParticipantRole>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let user_id = UserId::from_proto(request.user_id);
     let role = ChannelRole::from(request.role());
@@ -1837,7 +1542,7 @@ async fn set_room_participant_role(
 async fn call(
     request: proto::Call,
     response: Response<proto::Call>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let room_id = RoomId::from_proto(request.room_id);
     let calling_user_id = session.user_id();
@@ -1906,7 +1611,7 @@ async fn call(
 async fn cancel_call(
     request: proto::CancelCall,
     response: Response<proto::CancelCall>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let called_user_id = UserId::from_proto(request.called_user_id);
     let room_id = RoomId::from_proto(request.room_id);
@@ -1941,7 +1646,7 @@ async fn cancel_call(
 }
 
 /// Decline an incoming call.
-async fn decline_call(message: proto::DeclineCall, session: UserSession) -> Result<()> {
+async fn decline_call(message: proto::DeclineCall, session: Session) -> Result<()> {
     let room_id = RoomId::from_proto(message.room_id);
     {
         let room = session
@@ -1976,7 +1681,7 @@ async fn decline_call(message: proto::DeclineCall, session: UserSession) -> Resu
 async fn update_participant_location(
     request: proto::UpdateParticipantLocation,
     response: Response<proto::UpdateParticipantLocation>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let room_id = RoomId::from_proto(request.room_id);
     let location = request
@@ -1997,7 +1702,7 @@ async fn update_participant_location(
 async fn share_project(
     request: proto::ShareProject,
     response: Response<proto::ShareProject>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let (project_id, room) = &*session
         .db()
@@ -2007,9 +1712,6 @@ async fn share_project(
             session.connection_id,
             &request.worktrees,
             request.is_ssh_project,
-            request
-                .dev_server_project_id
-                .map(DevServerProjectId::from_proto),
         )
         .await?;
     response.send(proto::ShareProjectResponse {
@@ -2023,26 +1725,19 @@ async fn share_project(
 /// Unshare a project from the room.
 async fn unshare_project(message: proto::UnshareProject, session: Session) -> Result<()> {
     let project_id = ProjectId::from_proto(message.project_id);
-    unshare_project_internal(
-        project_id,
-        session.connection_id,
-        session.user_id(),
-        &session,
-    )
-    .await
+    unshare_project_internal(project_id, session.connection_id, &session).await
 }
 
 async fn unshare_project_internal(
     project_id: ProjectId,
     connection_id: ConnectionId,
-    user_id: Option<UserId>,
     session: &Session,
 ) -> Result<()> {
     let delete = {
         let room_guard = session
             .db()
             .await
-            .unshare_project(project_id, connection_id, user_id)
+            .unshare_project(project_id, connection_id)
             .await?;
 
         let (delete, room, guest_connection_ids) = &*room_guard;
@@ -2071,38 +1766,11 @@ async fn unshare_project_internal(
     Ok(())
 }
 
-/// DevServer makes a project available online
-async fn share_dev_server_project(
-    request: proto::ShareDevServerProject,
-    response: Response<proto::ShareDevServerProject>,
-    session: DevServerSession,
-) -> Result<()> {
-    let (dev_server_project, user_id, status) = session
-        .db()
-        .await
-        .share_dev_server_project(
-            DevServerProjectId::from_proto(request.dev_server_project_id),
-            session.dev_server_id(),
-            session.connection_id,
-            &request.worktrees,
-        )
-        .await?;
-    let Some(project_id) = dev_server_project.project_id else {
-        return Err(anyhow!("failed to share remote project"))?;
-    };
-
-    send_dev_server_projects_update(user_id, status, &session).await;
-
-    response.send(proto::ShareProjectResponse { project_id })?;
-
-    Ok(())
-}
-
 /// Join someone elses shared project.
 async fn join_project(
     request: proto::JoinProject,
     response: Response<proto::JoinProject>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let project_id = ProjectId::from_proto(request.project_id);
 
@@ -2133,7 +1801,7 @@ impl JoinProjectInternalResponse for Response<proto::JoinHostedProject> {
 
 fn join_project_internal(
     response: impl JoinProjectInternalResponse,
-    session: UserSession,
+    session: Session,
     project: &mut Project,
     replica_id: &ReplicaId,
 ) -> Result<()> {
@@ -2184,9 +1852,6 @@ fn join_project_internal(
         collaborators: collaborators.clone(),
         language_servers: project.language_servers.clone(),
         role: project.role.into(),
-        dev_server_project_id: project
-            .dev_server_project_id
-            .map(|dev_server_project_id| dev_server_project_id.0 as u64),
     })?;
 
     for (worktree_id, worktree) in mem::take(&mut project.worktrees) {
@@ -2252,7 +1917,7 @@ fn join_project_internal(
 }
 
 /// Leave someone elses shared project.
-async fn leave_project(request: proto::LeaveProject, session: UserSession) -> Result<()> {
+async fn leave_project(request: proto::LeaveProject, session: Session) -> Result<()> {
     let sender_id = session.connection_id;
     let project_id = ProjectId::from_proto(request.project_id);
     let db = session.db().await;
@@ -2279,7 +1944,7 @@ async fn leave_project(request: proto::LeaveProject, session: UserSession) -> Re
 async fn join_hosted_project(
     request: proto::JoinHostedProject,
     response: Response<proto::JoinHostedProject>,
-    session: UserSession,
+    session: Session,
 ) -> Result<()> {
     let (mut project, replica_id) = session
         .db()
@@ -2294,481 +1959,6 @@ async fn join_hosted_project(
     join_project_internal(response, session, &mut project, &replica_id)
 }
 
-async fn list_remote_directory(
-    request: proto::ListRemoteDirectory,
-    response: Response<proto::ListRemoteDirectory>,
-    session: UserSession,
-) -> Result<()> {
-    let dev_server_id = DevServerId(request.dev_server_id as i32);
-    let dev_server_connection_id = session
-        .connection_pool()
-        .await
-        .online_dev_server_connection_id(dev_server_id)?;
-
-    session
-        .db()
-        .await
-        .get_dev_server_for_user(dev_server_id, session.user_id())
-        .await?;
-
-    response.send(
-        session
-            .peer
-            .forward_request(session.connection_id, dev_server_connection_id, request)
-            .await?,
-    )?;
-    Ok(())
-}
-
-async fn update_dev_server_project(
-    request: proto::UpdateDevServerProject,
-    response: Response<proto::UpdateDevServerProject>,
-    session: UserSession,
-) -> Result<()> {
-    let dev_server_project_id = DevServerProjectId(request.dev_server_project_id as i32);
-
-    let (dev_server_project, update) = session
-        .db()
-        .await
-        .update_dev_server_project(dev_server_project_id, &request.paths, session.user_id())
-        .await?;
-
-    let projects = session
-        .db()
-        .await
-        .get_projects_for_dev_server(dev_server_project.dev_server_id)
-        .await?;
-
-    let dev_server_connection_id = session
-        .connection_pool()
-        .await
-        .online_dev_server_connection_id(dev_server_project.dev_server_id)?;
-
-    session.peer.send(
-        dev_server_connection_id,
-        proto::DevServerInstructions { projects },
-    )?;
-
-    send_dev_server_projects_update(session.user_id(), update, &session).await;
-
-    response.send(proto::Ack {})
-}
-
-async fn create_dev_server_project(
-    request: proto::CreateDevServerProject,
-    response: Response<proto::CreateDevServerProject>,
-    session: UserSession,
-) -> Result<()> {
-    let dev_server_id = DevServerId(request.dev_server_id as i32);
-    let dev_server_connection_id = session
-        .connection_pool()
-        .await
-        .dev_server_connection_id(dev_server_id);
-    let Some(dev_server_connection_id) = dev_server_connection_id else {
-        Err(ErrorCode::DevServerOffline
-            .message("Cannot create a remote project when the dev server is offline".to_string())
-            .anyhow())?
-    };
-
-    let path = request.path.clone();
-    //Check that the path exists on the dev server
-    session
-        .peer
-        .forward_request(
-            session.connection_id,
-            dev_server_connection_id,
-            proto::ValidateDevServerProjectRequest { path: path.clone() },
-        )
-        .await?;
-
-    let (dev_server_project, update) = session
-        .db()
-        .await
-        .create_dev_server_project(
-            DevServerId(request.dev_server_id as i32),
-            &request.path,
-            session.user_id(),
-        )
-        .await?;
-
-    let projects = session
-        .db()
-        .await
-        .get_projects_for_dev_server(dev_server_project.dev_server_id)
-        .await?;
-
-    session.peer.send(
-        dev_server_connection_id,
-        proto::DevServerInstructions { projects },
-    )?;
-
-    send_dev_server_projects_update(session.user_id(), update, &session).await;
-
-    response.send(proto::CreateDevServerProjectResponse {
-        dev_server_project: Some(dev_server_project.to_proto(None)),
-    })?;
-    Ok(())
-}
-
-async fn create_dev_server(
-    request: proto::CreateDevServer,
-    response: Response<proto::CreateDevServer>,
-    session: UserSession,
-) -> Result<()> {
-    let access_token = auth::random_token();
-    let hashed_access_token = auth::hash_access_token(&access_token);
-
-    if request.name.is_empty() {
-        return Err(proto::ErrorCode::Forbidden
-            .message("Dev server name cannot be empty".to_string())
-            .anyhow())?;
-    }
-
-    let (dev_server, status) = session
-        .db()
-        .await
-        .create_dev_server(
-            &request.name,
-            request.ssh_connection_string.as_deref(),
-            &hashed_access_token,
-            session.user_id(),
-        )
-        .await?;
-
-    send_dev_server_projects_update(session.user_id(), status, &session).await;
-
-    response.send(proto::CreateDevServerResponse {
-        dev_server_id: dev_server.id.0 as u64,
-        access_token: auth::generate_dev_server_token(dev_server.id.0 as usize, access_token),
-        name: request.name,
-    })?;
-    Ok(())
-}
-
-async fn regenerate_dev_server_token(
-    request: proto::RegenerateDevServerToken,
-    response: Response<proto::RegenerateDevServerToken>,
-    session: UserSession,
-) -> Result<()> {
-    let dev_server_id = DevServerId(request.dev_server_id as i32);
-    let access_token = auth::random_token();
-    let hashed_access_token = auth::hash_access_token(&access_token);
-
-    let connection_id = session
-        .connection_pool()
-        .await
-        .dev_server_connection_id(dev_server_id);
-    if let Some(connection_id) = connection_id {
-        shutdown_dev_server_internal(dev_server_id, connection_id, &session).await?;
-        session.peer.send(
-            connection_id,
-            proto::ShutdownDevServer {
-                reason: Some("dev server token was regenerated".to_string()),
-            },
-        )?;
-        let _ = remove_dev_server_connection(dev_server_id, &session).await;
-    }
-
-    let status = session
-        .db()
-        .await
-        .update_dev_server_token(dev_server_id, &hashed_access_token, session.user_id())
-        .await?;
-
-    send_dev_server_projects_update(session.user_id(), status, &session).await;
-
-    response.send(proto::RegenerateDevServerTokenResponse {
-        dev_server_id: dev_server_id.to_proto(),
-        access_token: auth::generate_dev_server_token(dev_server_id.0 as usize, access_token),
-    })?;
-    Ok(())
-}
-
-async fn rename_dev_server(
-    request: proto::RenameDevServer,
-    response: Response<proto::RenameDevServer>,
-    session: UserSession,
-) -> Result<()> {
-    if request.name.trim().is_empty() {
-        return Err(proto::ErrorCode::Forbidden
-            .message("Dev server name cannot be empty".to_string())
-            .anyhow())?;
-    }
-
-    let dev_server_id = DevServerId(request.dev_server_id as i32);
-    let dev_server = session.db().await.get_dev_server(dev_server_id).await?;
-    if dev_server.user_id != session.user_id() {
-        return Err(anyhow!(ErrorCode::Forbidden))?;
-    }
-
-    let status = session
-        .db()
-        .await
-        .rename_dev_server(
-            dev_server_id,
-            &request.name,
-            request.ssh_connection_string.as_deref(),
-            session.user_id(),
-        )
-        .await?;
-
-    send_dev_server_projects_update(session.user_id(), status, &session).await;
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn delete_dev_server(
-    request: proto::DeleteDevServer,
-    response: Response<proto::DeleteDevServer>,
-    session: UserSession,
-) -> Result<()> {
-    let dev_server_id = DevServerId(request.dev_server_id as i32);
-    let dev_server = session.db().await.get_dev_server(dev_server_id).await?;
-    if dev_server.user_id != session.user_id() {
-        return Err(anyhow!(ErrorCode::Forbidden))?;
-    }
-
-    let connection_id = session
-        .connection_pool()
-        .await
-        .dev_server_connection_id(dev_server_id);
-    if let Some(connection_id) = connection_id {
-        shutdown_dev_server_internal(dev_server_id, connection_id, &session).await?;
-        session.peer.send(
-            connection_id,
-            proto::ShutdownDevServer {
-                reason: Some("dev server was deleted".to_string()),
-            },
-        )?;
-        let _ = remove_dev_server_connection(dev_server_id, &session).await;
-    }
-
-    let status = session
-        .db()
-        .await
-        .delete_dev_server(dev_server_id, session.user_id())
-        .await?;
-
-    send_dev_server_projects_update(session.user_id(), status, &session).await;
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn delete_dev_server_project(
-    request: proto::DeleteDevServerProject,
-    response: Response<proto::DeleteDevServerProject>,
-    session: UserSession,
-) -> Result<()> {
-    let dev_server_project_id = DevServerProjectId(request.dev_server_project_id as i32);
-    let dev_server_project = session
-        .db()
-        .await
-        .get_dev_server_project(dev_server_project_id)
-        .await?;
-
-    let dev_server = session
-        .db()
-        .await
-        .get_dev_server(dev_server_project.dev_server_id)
-        .await?;
-    if dev_server.user_id != session.user_id() {
-        return Err(anyhow!(ErrorCode::Forbidden))?;
-    }
-
-    let dev_server_connection_id = session
-        .connection_pool()
-        .await
-        .dev_server_connection_id(dev_server.id);
-
-    if let Some(dev_server_connection_id) = dev_server_connection_id {
-        let project = session
-            .db()
-            .await
-            .find_dev_server_project(dev_server_project_id)
-            .await;
-        if let Ok(project) = project {
-            unshare_project_internal(
-                project.id,
-                dev_server_connection_id,
-                Some(session.user_id()),
-                &session,
-            )
-            .await?;
-        }
-    }
-
-    let (projects, status) = session
-        .db()
-        .await
-        .delete_dev_server_project(dev_server_project_id, dev_server.id, session.user_id())
-        .await?;
-
-    if let Some(dev_server_connection_id) = dev_server_connection_id {
-        session.peer.send(
-            dev_server_connection_id,
-            proto::DevServerInstructions { projects },
-        )?;
-    }
-
-    send_dev_server_projects_update(session.user_id(), status, &session).await;
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn rejoin_dev_server_projects(
-    request: proto::RejoinRemoteProjects,
-    response: Response<proto::RejoinRemoteProjects>,
-    session: UserSession,
-) -> Result<()> {
-    let mut rejoined_projects = {
-        let db = session.db().await;
-        db.rejoin_dev_server_projects(
-            &request.rejoined_projects,
-            session.user_id(),
-            session.0.connection_id,
-        )
-        .await?
-    };
-    response.send(proto::RejoinRemoteProjectsResponse {
-        rejoined_projects: rejoined_projects
-            .iter()
-            .map(|project| project.to_proto())
-            .collect(),
-    })?;
-    notify_rejoined_projects(&mut rejoined_projects, &session)
-}
-
-async fn reconnect_dev_server(
-    request: proto::ReconnectDevServer,
-    response: Response<proto::ReconnectDevServer>,
-    session: DevServerSession,
-) -> Result<()> {
-    let reshared_projects = {
-        let db = session.db().await;
-        db.reshare_dev_server_projects(
-            &request.reshared_projects,
-            session.dev_server_id(),
-            session.0.connection_id,
-        )
-        .await?
-    };
-
-    for project in &reshared_projects {
-        for collaborator in &project.collaborators {
-            session
-                .peer
-                .send(
-                    collaborator.connection_id,
-                    proto::UpdateProjectCollaborator {
-                        project_id: project.id.to_proto(),
-                        old_peer_id: Some(project.old_connection_id.into()),
-                        new_peer_id: Some(session.connection_id.into()),
-                    },
-                )
-                .trace_err();
-        }
-
-        broadcast(
-            Some(session.connection_id),
-            project
-                .collaborators
-                .iter()
-                .map(|collaborator| collaborator.connection_id),
-            |connection_id| {
-                session.peer.forward_send(
-                    session.connection_id,
-                    connection_id,
-                    proto::UpdateProject {
-                        project_id: project.id.to_proto(),
-                        worktrees: project.worktrees.clone(),
-                    },
-                )
-            },
-        );
-    }
-
-    response.send(proto::ReconnectDevServerResponse {
-        reshared_projects: reshared_projects
-            .iter()
-            .map(|project| proto::ResharedProject {
-                id: project.id.to_proto(),
-                collaborators: project
-                    .collaborators
-                    .iter()
-                    .map(|collaborator| collaborator.to_proto())
-                    .collect(),
-            })
-            .collect(),
-    })?;
-
-    Ok(())
-}
-
-async fn shutdown_dev_server(
-    _: proto::ShutdownDevServer,
-    response: Response<proto::ShutdownDevServer>,
-    session: DevServerSession,
-) -> Result<()> {
-    response.send(proto::Ack {})?;
-    shutdown_dev_server_internal(session.dev_server_id(), session.connection_id, &session).await?;
-    remove_dev_server_connection(session.dev_server_id(), &session).await
-}
-
-async fn shutdown_dev_server_internal(
-    dev_server_id: DevServerId,
-    connection_id: ConnectionId,
-    session: &Session,
-) -> Result<()> {
-    let (dev_server_projects, dev_server) = {
-        let db = session.db().await;
-        let dev_server_projects = db.get_projects_for_dev_server(dev_server_id).await?;
-        let dev_server = db.get_dev_server(dev_server_id).await?;
-        (dev_server_projects, dev_server)
-    };
-
-    for project_id in dev_server_projects.iter().filter_map(|p| p.project_id) {
-        unshare_project_internal(
-            ProjectId::from_proto(project_id),
-            connection_id,
-            None,
-            session,
-        )
-        .await?;
-    }
-
-    session
-        .connection_pool()
-        .await
-        .set_dev_server_offline(dev_server_id);
-
-    let status = session
-        .db()
-        .await
-        .dev_server_projects_update(dev_server.user_id)
-        .await?;
-    send_dev_server_projects_update(dev_server.user_id, status, session).await;
-
-    Ok(())
-}
-
-async fn remove_dev_server_connection(dev_server_id: DevServerId, session: &Session) -> Result<()> {
-    let dev_server_connection = session
-        .connection_pool()
-        .await
-        .dev_server_connection_id(dev_server_id);
-
-    if let Some(dev_server_connection) = dev_server_connection {
-        session
-            .connection_pool()
-            .await
-            .remove_connection(dev_server_connection)?;
-    }
-    Ok(())
-}
-
 /// Updates other participants with changes to the project
 async fn update_project(
     request: proto::UpdateProject,

crates/collab/src/rpc/connection_pool.rs 🔗

@@ -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)
-            );
-        }
     }
 }
 

crates/collab/src/tests.rs 🔗

@@ -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;

crates/collab/src/tests/dev_server_tests.rs 🔗

@@ -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!");
-}

crates/collab/src/tests/test_server.rs 🔗

@@ -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()

crates/dev_server_projects/Cargo.toml 🔗

@@ -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

crates/dev_server_projects/src/dev_server_projects.rs 🔗

@@ -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(())
-        })
-    }
-}

crates/headless/Cargo.toml 🔗

@@ -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"] }

crates/headless/src/headless.rs 🔗

@@ -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(())
-        })
-    }
-}

crates/project/Cargo.toml 🔗

@@ -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

crates/project/src/project.rs 🔗

@@ -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")))
         }

crates/project/src/terminals.rs 🔗

@@ -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);

crates/project/src/worktree_store.rs 🔗

@@ -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));

crates/project_panel/src/project_panel.rs 🔗

@@ -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())
+        })
     }
 }
 

crates/proto/proto/zed.proto 🔗

@@ -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;
 }

crates/proto/src/proto.rs 🔗

@@ -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),

crates/recent_projects/Cargo.toml 🔗

@@ -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

crates/recent_projects/src/disconnected_overlay.rs 🔗

@@ -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) => {

crates/recent_projects/src/recent_projects.rs 🔗

@@ -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 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();
-
-                            cx.spawn(|_, mut cx| async move {
-                                open_ssh_project(connection_options, paths, app_state, open_options, nickname, &mut cx).await
-                            })
+
+                                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 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
+                                })
+                            }
                         }
                     }
-                }
                 })
-            .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,

crates/recent_projects/src/remote_servers.rs 🔗

@@ -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(())
-}

crates/title_bar/Cargo.toml 🔗

@@ -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

crates/title_bar/src/collab.rs 🔗

@@ -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",

crates/title_bar/src/title_bar.rs 🔗

@@ -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);
         }

crates/workspace/Cargo.toml 🔗

@@ -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

crates/workspace/src/persistence.rs 🔗

@@ -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())));

crates/workspace/src/persistence/model.rs 🔗

@@ -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 {

crates/workspace/src/workspace.rs 🔗

@@ -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,

crates/zed/Cargo.toml 🔗

@@ -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

crates/zed/src/main.rs 🔗

@@ -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!");
-                        }
-                    })
-                    .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)
-                            }
+                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)) {

crates/zed/src/zed/open_listener.rs 🔗

@@ -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(_) => {}
             }
         }