From c0ebb401c462e527f74fe4588bf7f5edb41391ab Mon Sep 17 00:00:00 2001 From: KyleBarton Date: Tue, 23 Dec 2025 21:46:33 -0800 Subject: [PATCH] First pass at the modal for setting up devcontainer from scratch. --- crates/recent_projects/src/dev_container.rs | 127 +++++++++++++++++- crates/recent_projects/src/recent_projects.rs | 2 +- crates/zed/src/main.rs | 3 +- 3 files changed, 128 insertions(+), 4 deletions(-) diff --git a/crates/recent_projects/src/dev_container.rs b/crates/recent_projects/src/dev_container.rs index 0e6b8b381df32d688e062948460707a5f8cfb552..97967ea53ccd61619e7d21482b99542e9753e293 100644 --- a/crates/recent_projects/src/dev_container.rs +++ b/crates/recent_projects/src/dev_container.rs @@ -1,12 +1,19 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; -use gpui::AsyncWindowContext; +use gpui::{ + Action, AsyncWindowContext, DismissEvent, EventEmitter, FocusHandle, Focusable, RenderOnce, +}; use node_runtime::NodeRuntime; use serde::Deserialize; use settings::DevContainerConnection; use smol::fs; -use workspace::Workspace; +use ui::{ + App, Color, Context, Headline, HeadlineSize, Icon, IconName, InteractiveElement, IntoElement, + Label, ListItem, ListSeparator, ModalHeader, Navigable, NavigableEntry, ParentElement, Render, + Styled, StyledExt, Toggleable, Window, div, rems, +}; +use workspace::{ModalView, Workspace, with_active_or_new_workspace}; use crate::remote_connections::Connection; @@ -275,6 +282,122 @@ pub(crate) enum DevContainerError { DevContainerParseFailed, } +#[derive(PartialEq, Clone, Deserialize, Default, Action)] +#[action(namespace = containers)] +#[serde(deny_unknown_fields)] +pub struct InitDevContainer; + +pub fn init(cx: &mut App) { + cx.on_action(|_: &InitDevContainer, cx| { + with_active_or_new_workspace(cx, move |workspace, window, cx| { + workspace.toggle_modal(window, cx, |window, cx| DevContainerModal::new(window, cx)); + }); + }); +} + +struct DevContainerModal { + focus_handle: FocusHandle, + search_navigable_entry: NavigableEntry, + other_navigable_entry: NavigableEntry, +} + +impl DevContainerModal { + fn new(window: &mut Window, cx: &mut App) -> Self { + let search_navigable_entry = NavigableEntry::focusable(cx); + let other_navigable_entry = NavigableEntry::focusable(cx); + let focus_handle = cx.focus_handle(); + DevContainerModal { + focus_handle, + search_navigable_entry, + other_navigable_entry, + } + } + + fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context) { + cx.emit(DismissEvent); + } +} + +impl ModalView for DevContainerModal {} +impl EventEmitter for DevContainerModal {} +impl Focusable for DevContainerModal { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for DevContainerModal { + fn render( + &mut self, + window: &mut ui::Window, + cx: &mut ui::Context, + ) -> impl ui::IntoElement { + let mut view = + Navigable::new( + div() + .child(div().track_focus(&self.focus_handle).child( + ModalHeader::new().child( + Headline::new("Create Dev Container").size(HeadlineSize::XSmall), + ), + )) + .child(ListSeparator) + .child( + div() + .track_focus(&self.search_navigable_entry.focus_handle) + .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| { + println!("action on search containers"); + })) + .child( + ListItem::new("li-search-containers") + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Pencil).color(Color::Muted)) + .toggle_state( + self.search_navigable_entry + .focus_handle + .contains_focused(window, cx), + ) + .child(Label::new("Search for dev containers in registry")), + ), + ) + .child( + div() + .track_focus(&self.other_navigable_entry.focus_handle) + .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| { + println!("action on other containers"); + })) + .child( + ListItem::new("li-search-containers") + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Pencil).color(Color::Muted)) + .toggle_state( + self.other_navigable_entry + .focus_handle + .contains_focused(window, cx), + ) + .child(Label::new("Do another thing")), + ), + ) + .into_any_element(), + ); + view = view.entry(self.search_navigable_entry.clone()); + view = view.entry(self.other_navigable_entry.clone()); + + // // This is an interesting edge. Can't focus in render, or you'll just override whatever was focused before. + // // self.search_navigable_entry.focus_handle.focus(window, cx); + + // view.render(window, cx).into_any_element() + div() + .elevation_3(cx) + .w(rems(34.)) + // WHY IS THIS NEEDED FOR ACTION DISPATCH OMG + .key_context("ContainerModal") + .on_action(cx.listener(Self::dismiss)) + .child(view.render(window, cx).into_any_element()) + } +} + #[cfg(test)] mod test { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 435933a880123c00d3f3fbaaea2c54f6554f0d3b..335c2c4e04846d1cb6e6e3e02631aecef524c2eb 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -1,4 +1,4 @@ -mod dev_container; +pub mod dev_container; mod dev_container_suggest; pub mod disconnected_overlay; mod remote_connections; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8c6575780bbc418b4717af8329c51a057eb608d3..32c08ba1eef01efb3dabdee18a4c3f3485826141 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -33,7 +33,7 @@ use assets::Assets; use node_runtime::{NodeBinaryOptions, NodeRuntime}; use parking_lot::Mutex; use project::{project_settings::ProjectSettings, trusted_worktrees}; -use recent_projects::{SshSettings, open_remote_project}; +use recent_projects::{SshSettings, dev_container, open_remote_project}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use session::{AppSession, Session}; use settings::{BaseKeymap, Settings, SettingsStore, watch_config_file}; @@ -616,6 +616,7 @@ fn main() { agent_ui_v2::agents_panel::init(cx); repl::init(app_state.fs.clone(), cx); recent_projects::init(cx); + dev_container::init(cx); load_embedded_fonts(cx);