Auto-open remote projects on creation (#11826)

Conrad Irwin created

Release Notes:

- N/A

Change summary

crates/recent_projects/src/dev_servers.rs  | 278 +++++++++++++----------
crates/ui/src/components/list/list_item.rs |   2 
2 files changed, 154 insertions(+), 126 deletions(-)

Detailed changes

crates/recent_projects/src/dev_servers.rs 🔗

@@ -4,6 +4,7 @@ use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerPro
 use editor::Editor;
 use feature_flags::FeatureFlagAppExt;
 use feature_flags::FeatureFlagViewExt;
+use gpui::Subscription;
 use gpui::{
     percentage, Action, Animation, AnimationExt, AnyElement, AppContext, ClipboardItem,
     DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ScrollHandle, Transformation,
@@ -53,10 +54,10 @@ enum EditDevServerState {
     RegeneratedToken(RegenerateDevServerTokenResponse),
 }
 
-#[derive(Clone)]
 struct CreateDevServerProject {
     dev_server_id: DevServerId,
     creating: bool,
+    _opening: Option<Subscription>,
 }
 
 enum Mode {
@@ -94,7 +95,7 @@ impl DevServerProjects {
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let project_path_input = cx.new_view(|cx| {
             let mut editor = Editor::single_line(cx);
-            editor.set_placeholder_text("Project path", cx);
+            editor.set_placeholder_text("Project path (~/work/zed, /workspace/zed, …)", cx);
             editor
         });
         let dev_server_name_input =
@@ -165,15 +166,45 @@ impl DevServerProjects {
         cx.spawn(|this, mut cx| async move {
             let result = create.await;
             this.update(&mut cx, |this, cx| {
-                if result.is_ok() {
-                    this.project_path_input.update(cx, |editor, cx| {
-                        editor.set_text("", cx);
-                    });
-                    this.mode = Mode::Default(None);
+                if let Ok(result) = &result {
+                    if let Some(dev_server_project_id) =
+                        result.dev_server_project.as_ref().map(|p| p.id)
+                    {
+                        let subscription =
+                            cx.observe(&this.dev_server_store, move |this, store, cx| {
+                                if let Some(project_id) = store
+                                    .read(cx)
+                                    .dev_server_project(DevServerProjectId(dev_server_project_id))
+                                    .and_then(|p| p.project_id)
+                                {
+                                    this.project_path_input.update(cx, |editor, cx| {
+                                        editor.set_text("", cx);
+                                    });
+                                    this.mode = Mode::Default(None);
+                                    if let Some(app_state) = AppState::global(cx).upgrade() {
+                                        workspace::join_dev_server_project(
+                                            project_id, app_state, None, cx,
+                                        )
+                                        .detach_and_prompt_err(
+                                            "Could not join project",
+                                            cx,
+                                            |_, _| None,
+                                        )
+                                    }
+                                }
+                            });
+
+                        this.mode = Mode::Default(Some(CreateDevServerProject {
+                            dev_server_id,
+                            creating: true,
+                            _opening: Some(subscription),
+                        }));
+                    }
                 } else {
                     this.mode = Mode::Default(Some(CreateDevServerProject {
                         dev_server_id,
                         creating: false,
+                        _opening: None,
                     }));
                 }
             })
@@ -196,6 +227,7 @@ impl DevServerProjects {
         self.mode = Mode::Default(Some(CreateDevServerProject {
             dev_server_id,
             creating: true,
+            _opening: None,
         }));
     }
 
@@ -443,109 +475,74 @@ impl DevServerProjects {
     fn render_dev_server(
         &mut self,
         dev_server: &DevServer,
-        mut create_project: Option<CreateDevServerProject>,
+        create_project: Option<bool>,
         cx: &mut ViewContext<Self>,
     ) -> impl IntoElement {
         let dev_server_id = dev_server.id;
         let status = dev_server.status;
         let dev_server_name = dev_server.name.clone();
-        if create_project
-            .as_ref()
-            .is_some_and(|cp| cp.dev_server_id != dev_server.id)
-        {
-            create_project = None;
-        }
 
         v_flex()
             .w_full()
             .child(
-                h_flex()
-                    .group("dev-server")
-                    .justify_between()
-                    .child(
-                        h_flex()
-                            .gap_2()
-                            .child(
-                                div()
-                                    .id(("status", dev_server.id.0))
-                                    .relative()
-                                    .child(Icon::new(IconName::Server).size(IconSize::Small))
-                                    .child(
-                                        div().absolute().bottom_0().left(rems_from_px(8.0)).child(
-                                            Indicator::dot().color(match status {
-                                                DevServerStatus::Online => Color::Created,
-                                                DevServerStatus::Offline => Color::Hidden,
-                                            }),
-                                        ),
-                                    )
-                                    .tooltip(move |cx| {
-                                        Tooltip::text(
-                                            match status {
-                                                DevServerStatus::Online => "Online",
-                                                DevServerStatus::Offline => "Offline",
-                                            },
-                                            cx,
-                                        )
+                h_flex().group("dev-server").justify_between().child(
+                    h_flex()
+                        .gap_2()
+                        .child(
+                            div()
+                                .id(("status", dev_server.id.0))
+                                .relative()
+                                .child(Icon::new(IconName::Server).size(IconSize::Small))
+                                .child(div().absolute().bottom_0().left(rems_from_px(8.0)).child(
+                                    Indicator::dot().color(match status {
+                                        DevServerStatus::Online => Color::Created,
+                                        DevServerStatus::Offline => Color::Hidden,
                                     }),
-                            )
-                            .child(dev_server_name.clone())
-                            .child(
-                                h_flex()
-                                    .visible_on_hover("dev-server")
-                                    .gap_1()
-                                    .child(
-                                        IconButton::new("edit-dev-server", IconName::Pencil)
-                                            .on_click(cx.listener(move |this, _, cx| {
-                                                this.mode = Mode::EditDevServer(EditDevServer {
-                                                    dev_server_id,
-                                                    state: EditDevServerState::Default,
-                                                });
-                                                let dev_server_name = dev_server_name.clone();
-                                                this.rename_dev_server_input.update(
-                                                    cx,
-                                                    move |input, cx| {
-                                                        input.editor().update(
-                                                            cx,
-                                                            move |editor, cx| {
-                                                                editor.set_text(dev_server_name, cx)
-                                                            },
-                                                        )
-                                                    },
-                                                )
-                                            }))
-                                            .tooltip(|cx| Tooltip::text("Edit dev server", cx)),
+                                ))
+                                .tooltip(move |cx| {
+                                    Tooltip::text(
+                                        match status {
+                                            DevServerStatus::Online => "Online",
+                                            DevServerStatus::Offline => "Offline",
+                                        },
+                                        cx,
                                     )
-                                    .child({
-                                        let dev_server_id = dev_server.id;
-                                        IconButton::new("remove-dev-server", IconName::Trash)
-                                            .on_click(cx.listener(move |this, _, cx| {
-                                                this.delete_dev_server(dev_server_id, cx)
-                                            }))
-                                            .tooltip(|cx| Tooltip::text("Remove dev server", cx))
-                                    }),
-                            ),
-                    )
-                    .child(
-                        h_flex().gap_1().child(
-                            IconButton::new(
-                                ("add-remote-project", dev_server_id.0),
-                                IconName::Plus,
-                            )
-                            .tooltip(|cx| Tooltip::text("Add a remote project", cx))
-                            .on_click(cx.listener(
-                                move |this, _, cx| {
-                                    if let Mode::Default(project) = &mut this.mode {
-                                        *project = Some(CreateDevServerProject {
-                                            dev_server_id,
-                                            creating: false,
-                                        });
-                                    }
-                                    this.project_path_input.read(cx).focus_handle(cx).focus(cx);
-                                    cx.notify();
-                                },
-                            )),
+                                }),
+                        )
+                        .child(dev_server_name.clone())
+                        .child(
+                            h_flex()
+                                .visible_on_hover("dev-server")
+                                .gap_1()
+                                .child(
+                                    IconButton::new("edit-dev-server", IconName::Pencil)
+                                        .on_click(cx.listener(move |this, _, cx| {
+                                            this.mode = Mode::EditDevServer(EditDevServer {
+                                                dev_server_id,
+                                                state: EditDevServerState::Default,
+                                            });
+                                            let dev_server_name = dev_server_name.clone();
+                                            this.rename_dev_server_input.update(
+                                                cx,
+                                                move |input, cx| {
+                                                    input.editor().update(cx, move |editor, cx| {
+                                                        editor.set_text(dev_server_name, cx)
+                                                    })
+                                                },
+                                            )
+                                        }))
+                                        .tooltip(|cx| Tooltip::text("Edit dev server", cx)),
+                                )
+                                .child({
+                                    let dev_server_id = dev_server.id;
+                                    IconButton::new("remove-dev-server", IconName::Trash)
+                                        .on_click(cx.listener(move |this, _, cx| {
+                                            this.delete_dev_server(dev_server_id, cx)
+                                        }))
+                                        .tooltip(|cx| Tooltip::text("Remove dev server", cx))
+                                }),
                         ),
-                    ),
+                ),
             )
             .child(
                 v_flex()
@@ -567,8 +564,32 @@ impl DevServerProjects {
                                     .iter()
                                     .map(|p| self.render_dev_server_project(p, cx)),
                             )
-                            .when_some(create_project, |el, create_project| {
-                                el.child(self.render_create_new_project(&create_project, cx))
+                            .when(
+                                create_project.is_none()
+                                    && dev_server.status == DevServerStatus::Online,
+                                |el| {
+                                    el.child(
+                                        ListItem::new("new-remote_project")
+                                            .start_slot(Icon::new(IconName::Plus))
+                                            .child(Label::new("Open folder…"))
+                                            .on_click(cx.listener(move |this, _, cx| {
+                                                this.mode =
+                                                    Mode::Default(Some(CreateDevServerProject {
+                                                        dev_server_id,
+                                                        creating: false,
+                                                        _opening: None,
+                                                    }));
+                                                this.project_path_input
+                                                    .read(cx)
+                                                    .focus_handle(cx)
+                                                    .focus(cx);
+                                                cx.notify();
+                                            })),
+                                    )
+                                },
+                            )
+                            .when_some(create_project, |el, creating| {
+                                el.child(self.render_create_new_project(creating, cx))
                             }),
                     ),
             )
@@ -576,29 +597,24 @@ impl DevServerProjects {
 
     fn render_create_new_project(
         &mut self,
-        create_project: &CreateDevServerProject,
+        creating: bool,
         _: &mut ViewContext<Self>,
     ) -> impl IntoElement {
         ListItem::new("create-remote-project")
+            .disabled(true)
             .start_slot(Icon::new(IconName::FileTree).color(Color::Muted))
             .child(self.project_path_input.clone())
-            .child(
-                div()
-                    .w(IconSize::Medium.rems())
-                    .when(create_project.creating, |el| {
-                        el.child(
-                            Icon::new(IconName::ArrowCircle)
-                                .size(IconSize::Medium)
-                                .with_animation(
-                                    "arrow-circle",
-                                    Animation::new(Duration::from_secs(2)).repeat(),
-                                    |icon, delta| {
-                                        icon.transform(Transformation::rotate(percentage(delta)))
-                                    },
-                                ),
-                        )
-                    }),
-            )
+            .child(div().w(IconSize::Medium.rems()).when(creating, |el| {
+                el.child(
+                    Icon::new(IconName::ArrowCircle)
+                        .size(IconSize::Medium)
+                        .with_animation(
+                            "arrow-circle",
+                            Animation::new(Duration::from_secs(2)).repeat(),
+                            |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
+                        ),
+                )
+            }))
     }
 
     fn render_dev_server_project(
@@ -920,7 +936,18 @@ impl DevServerProjects {
         let Mode::Default(create_dev_server_project) = &self.mode else {
             unreachable!()
         };
-        let create_dev_server_project = create_dev_server_project.clone();
+
+        let mut is_creating = None;
+        let mut creating_dev_server = None;
+        if let Some(CreateDevServerProject {
+            creating,
+            dev_server_id,
+            ..
+        }) = create_dev_server_project
+        {
+            is_creating = Some(*creating);
+            creating_dev_server = Some(*dev_server_id);
+        };
 
         v_flex()
             .id("scroll-container")
@@ -960,12 +987,13 @@ impl DevServerProjects {
                             ),
                         ))
                         .children(dev_servers.iter().map(|dev_server| {
-                            self.render_dev_server(
-                                dev_server,
-                                create_dev_server_project.clone(),
-                                cx,
-                            )
-                            .into_any_element()
+                            let creating = if creating_dev_server == Some(dev_server.id) {
+                                is_creating
+                            } else {
+                                None
+                            };
+                            self.render_dev_server(dev_server, creating, cx)
+                                .into_any_element()
                         })),
                 ),
             )

crates/ui/src/components/list/list_item.rs 🔗

@@ -157,7 +157,7 @@ impl RenderOnce for ListItem {
                 this.ml(self.indent_level as f32 * self.indent_step_size)
                     .px_2()
             })
-            .when(!self.inset, |this| {
+            .when(!self.inset && !self.disabled, |this| {
                 this
                     // TODO: Add focus state
                     // .when(self.state == InteractionState::Focused, |this| {