use std::path::Path; use gpui::AppContext;use gpui::Entity;use gpui::Task;use http_client::anyhow;use picker::Picker;use picker::PickerDelegate;use settings::RegisterSetting;use settings::Settings;use std::collections::HashMap;use std::collections::HashSet;use std::fmt::Debug;use std::fmt::Display;use std::sync::Arc;use ui::ActiveTheme;use ui::Button;use ui::Clickable;use ui::FluentBuilder;use ui::KeyBinding;use ui::StatefulInteractiveElement;use ui::Switch;use ui::ToggleState;use ui::Tooltip;use ui::h_flex;use ui::rems_from_px;use ui::v_flex; use gpui::{Action, DismissEvent, EventEmitter, FocusHandle, Focusable, RenderOnce, WeakEntity};use serde::Deserialize;use ui::{ AnyElement, App, Color, CommonAnimationExt, Context, Headline, HeadlineSize, Icon, IconName, InteractiveElement, IntoElement, Label, ListItem, ListSeparator, ModalHeader, Navigable, NavigableEntry, ParentElement, Render, Styled, StyledExt, Toggleable, Window, div, rems,};use util::ResultExt;use util::rel_path::RelPath;use workspace::{ModalView, Workspace, with_active_or_new_workspace}; use futures::AsyncReadExt;use http::Request;use http_client::{AsyncBody, HttpClient}; mod devcontainer_api; use devcontainer_api::ensure_devcontainer_cli;use devcontainer_api::read_devcontainer_configuration; use crate::devcontainer_api::DevContainerError;use crate::devcontainer_api::apply_dev_container_template; pub use devcontainer_api::{ DevContainerConfig, find_configs_in_snapshot, find_devcontainer_configs, start_dev_container_with_config,}; pub struct DevContainerContext { pub project_directory: Arc<Path>, pub use_podman: bool, pub node_runtime: node_runtime::NodeRuntime,} impl DevContainerContext { pub fn from_workspace(workspace: &Workspace, cx: &App) -> Option<Self> { let project_directory = workspace.project().read(cx).active_project_directory(cx)?; let use_podman = DevContainerSettings::get_global(cx).use_podman; let node_runtime = workspace.app_state().node_runtime.clone(); Some(Self { project_directory, use_podman, node_runtime, }) }} #[derive(RegisterSetting)]struct DevContainerSettings { use_podman: bool,} pub fn use_podman(cx: &App) -> bool { DevContainerSettings::get_global(cx).use_podman} impl Settings for DevContainerSettings { fn from_settings(content: &settings::SettingsContent) -> Self { Self { use_podman: content.remote.use_podman.unwrap_or(false), } }} #[derive(PartialEq, Clone, Deserialize, Default, Action)]#[action(namespace = projects)]#[serde(deny_unknown_fields)]struct InitializeDevContainer; pub fn init(cx: &mut App) { cx.on_action(|_: &InitializeDevContainer, cx| { with_active_or_new_workspace(cx, move |workspace, window, cx| { let weak_entity = cx.weak_entity(); workspace.toggle_modal(window, cx, |window, cx| { DevContainerModal::new(weak_entity, window, cx) }); }); });} #[derive(Clone)]struct TemplateEntry { template: DevContainerTemplate, options_selected: HashMap<String, String>, current_option_index: usize, current_option: Option<TemplateOptionSelection>, features_selected: HashSet<DevContainerFeature>,} #[derive(Clone)]struct FeatureEntry { feature: DevContainerFeature, toggle_state: ToggleState,} #[derive(Clone)]struct TemplateOptionSelection { option_name: String, description: String, navigable_options: Vec<(String, NavigableEntry)>,} impl Eq for TemplateEntry {}impl PartialEq for TemplateEntry { fn eq(&self, other: &Self) -> bool { self.template == other.template }}impl Debug for TemplateEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TemplateEntry") .field("template", &self.template) .finish() }} impl Eq for FeatureEntry {}impl PartialEq for FeatureEntry { fn eq(&self, other: &Self) -> bool { self.feature == other.feature }} impl Debug for FeatureEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("FeatureEntry") .field("feature", &self.feature) .finish() }} #[derive(Debug, Clone, PartialEq, Eq)]enum DevContainerState { Initial, QueryingTemplates, TemplateQueryReturned(Result<Vec<TemplateEntry>, String>), QueryingFeatures(TemplateEntry), FeaturesQueryReturned(TemplateEntry), UserOptionsSpecifying(TemplateEntry), ConfirmingWriteDevContainer(TemplateEntry), TemplateWriteFailed(DevContainerError),} #[derive(Debug, Clone)]enum DevContainerMessage { SearchTemplates, TemplatesRetrieved(Vec<DevContainerTemplate>), ErrorRetrievingTemplates(String), TemplateSelected(TemplateEntry), TemplateOptionsSpecified(TemplateEntry), TemplateOptionsCompleted(TemplateEntry), FeaturesRetrieved(Vec<DevContainerFeature>), FeaturesSelected(TemplateEntry), NeedConfirmWriteDevContainer(TemplateEntry), ConfirmWriteDevContainer(TemplateEntry), FailedToWriteTemplate(DevContainerError), GoBack,} struct DevContainerModal { workspace: WeakEntity<Workspace>, picker: Option<Entity<Picker<TemplatePickerDelegate>>>, features_picker: Option<Entity<Picker<FeaturePickerDelegate>>>, focus_handle: FocusHandle, confirm_entry: NavigableEntry, back_entry: NavigableEntry, state: DevContainerState,} struct TemplatePickerDelegate { selected_index: usize, placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, candidate_templates: Vec<TemplateEntry>, matching_indices: Vec<usize>, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >,} impl TemplatePickerDelegate { fn new( placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, elements: Vec<TemplateEntry>, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >, ) -> Self { Self { selected_index: 0, placeholder_text, stateful_modal, candidate_templates: elements, matching_indices: Vec::new(), on_confirm, } }} impl PickerDelegate for TemplatePickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.matching_indices.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) { self.selected_index = ix; } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { self.placeholder_text.clone().into() } fn update_matches( &mut self, query: String, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) -> gpui::Task<()> { self.matching_indices = self .candidate_templates .iter() .enumerate() .filter(|(_, template_entry)| { template_entry .template .id .to_lowercase() .contains(&query.to_lowercase()) || template_entry .template .name .to_lowercase() .contains(&query.to_lowercase()) }) .map(|(ix, _)| ix) .collect(); self.selected_index = std::cmp::min( self.selected_index, self.matching_indices.len().saturating_sub(1), ); Task::ready(()) } fn confirm( &mut self, _secondary: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) { let fun = &mut self.on_confirm; if self.matching_indices.is_empty() { return; } self.stateful_modal .update(cx, |modal, cx| { let Some(confirmed_entry) = self .matching_indices .get(self.selected_index) .and_then(|ix| self.candidate_templates.get(*ix)) else { log::error!("Selected index not in range of known matches"); return; }; fun(confirmed_entry.clone(), modal, window, cx); }) .ok(); } fn dismissed(&mut self, window: &mut Window, cx: &mut Context<picker::Picker<Self>>) { self.stateful_modal .update(cx, |modal, cx| { modal.dismiss(&menu::Cancel, window, cx); }) .ok(); } fn render_match( &self, ix: usize, selected: bool, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) -> Option<Self::ListItem> { let Some(template_entry) = self.candidate_templates.get(self.matching_indices[ix]) else { return None; }; Some( ListItem::new("li-template-match") .inset(true) .spacing(ui::ListItemSpacing::Sparse) .start_slot(Icon::new(IconName::Box)) .toggle_state(selected) .child(Label::new(template_entry.template.name.clone())) .into_any_element(), ) } fn render_footer( &self, _window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<AnyElement> { Some( h_flex() .w_full() .p_1p5() .gap_1() .justify_start() .border_t_1() .border_color(cx.theme().colors().border_variant) .child( Button::new("run-action", "Continue") .key_binding( KeyBinding::for_action(&menu::Confirm, cx) .map(|kb| kb.size(rems_from_px(12.))), ) .on_click(|_, window, cx| { window.dispatch_action(menu::Confirm.boxed_clone(), cx) }), ) .into_any_element(), ) }} struct FeaturePickerDelegate { selected_index: usize, placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, candidate_features: Vec<FeatureEntry>, template_entry: TemplateEntry, matching_indices: Vec<usize>, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >,} impl FeaturePickerDelegate { fn new( placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, candidate_features: Vec<FeatureEntry>, template_entry: TemplateEntry, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >, ) -> Self { Self { selected_index: 0, placeholder_text, stateful_modal, candidate_features, template_entry, matching_indices: Vec::new(), on_confirm, } }} impl PickerDelegate for FeaturePickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.matching_indices.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) { self.selected_index = ix; } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { self.placeholder_text.clone().into() } fn update_matches( &mut self, query: String, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Task<()> { self.matching_indices = self .candidate_features .iter() .enumerate() .filter(|(_, feature_entry)| { feature_entry .feature .id .to_lowercase() .contains(&query.to_lowercase()) || feature_entry .feature .name .to_lowercase() .contains(&query.to_lowercase()) }) .map(|(ix, _)| ix) .collect(); self.selected_index = std::cmp::min( self.selected_index, self.matching_indices.len().saturating_sub(1), ); Task::ready(()) } fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) { if secondary { self.stateful_modal .update(cx, |modal, cx| { (self.on_confirm)(self.template_entry.clone(), modal, window, cx) }) .ok(); } else { if self.matching_indices.is_empty() { return; } let Some(current) = self .matching_indices .get(self.selected_index) .and_then(|ix| self.candidate_features.get_mut(*ix)) else { log::error!("Selected index not in range of matches"); return; }; current.toggle_state = match current.toggle_state { ToggleState::Selected => { self.template_entry .features_selected .remove(¤t.feature);Lines 1–500 of 1886. Load more
use std::path::Path; use gpui::AppContext; use gpui::Entity; use gpui::Task; use http_client::anyhow; use picker::Picker; use picker::PickerDelegate; use settings::RegisterSetting; use settings::Settings; use std::collections::HashMap; use std::collections::HashSet; use std::fmt::Debug; use std::fmt::Display; use std::sync::Arc; use ui::ActiveTheme; use ui::Button; use ui::Clickable; use ui::FluentBuilder; use ui::KeyBinding; use ui::StatefulInteractiveElement; use ui::Switch; use ui::ToggleState; use ui::Tooltip; use ui::h_flex; use ui::rems_from_px; use ui::v_flex; use gpui::{Action, DismissEvent, EventEmitter, FocusHandle, Focusable, RenderOnce, WeakEntity}; use serde::Deserialize; use ui::{ AnyElement, App, Color, CommonAnimationExt, Context, Headline, HeadlineSize, Icon, IconName, InteractiveElement, IntoElement, Label, ListItem, ListSeparator, ModalHeader, Navigable, NavigableEntry, ParentElement, Render, Styled, StyledExt, Toggleable, Window, div, rems, }; use util::ResultExt; use util::rel_path::RelPath; use workspace::{ModalView, Workspace, with_active_or_new_workspace}; use futures::AsyncReadExt; use http::Request; use http_client::{AsyncBody, HttpClient}; mod devcontainer_api; use devcontainer_api::ensure_devcontainer_cli; use devcontainer_api::read_devcontainer_configuration; use crate::devcontainer_api::DevContainerError; use crate::devcontainer_api::apply_dev_container_template; pub use devcontainer_api::{ DevContainerConfig, find_configs_in_snapshot, find_devcontainer_configs, start_dev_container_with_config, }; pub struct DevContainerContext { pub project_directory: Arc<Path>, pub use_podman: bool, pub node_runtime: node_runtime::NodeRuntime, } impl DevContainerContext { pub fn from_workspace(workspace: &Workspace, cx: &App) -> Option<Self> { let project_directory = workspace.project().read(cx).active_project_directory(cx)?; let use_podman = DevContainerSettings::get_global(cx).use_podman; let node_runtime = workspace.app_state().node_runtime.clone(); Some(Self { project_directory, use_podman, node_runtime, }) } } #[derive(RegisterSetting)] struct DevContainerSettings { use_podman: bool, } pub fn use_podman(cx: &App) -> bool { DevContainerSettings::get_global(cx).use_podman } impl Settings for DevContainerSettings { fn from_settings(content: &settings::SettingsContent) -> Self { Self { use_podman: content.remote.use_podman.unwrap_or(false), } } } #[derive(PartialEq, Clone, Deserialize, Default, Action)] #[action(namespace = projects)] #[serde(deny_unknown_fields)] struct InitializeDevContainer; pub fn init(cx: &mut App) { cx.on_action(|_: &InitializeDevContainer, cx| { with_active_or_new_workspace(cx, move |workspace, window, cx| { let weak_entity = cx.weak_entity(); workspace.toggle_modal(window, cx, |window, cx| { DevContainerModal::new(weak_entity, window, cx) }); }); }); } #[derive(Clone)] struct TemplateEntry { template: DevContainerTemplate, options_selected: HashMap<String, String>, current_option_index: usize, current_option: Option<TemplateOptionSelection>, features_selected: HashSet<DevContainerFeature>, } #[derive(Clone)] struct FeatureEntry { feature: DevContainerFeature, toggle_state: ToggleState, } #[derive(Clone)] struct TemplateOptionSelection { option_name: String, description: String, navigable_options: Vec<(String, NavigableEntry)>, } impl Eq for TemplateEntry {} impl PartialEq for TemplateEntry { fn eq(&self, other: &Self) -> bool { self.template == other.template } } impl Debug for TemplateEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TemplateEntry") .field("template", &self.template) .finish() } } impl Eq for FeatureEntry {} impl PartialEq for FeatureEntry { fn eq(&self, other: &Self) -> bool { self.feature == other.feature } } impl Debug for FeatureEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("FeatureEntry") .field("feature", &self.feature) .finish() } } #[derive(Debug, Clone, PartialEq, Eq)] enum DevContainerState { Initial, QueryingTemplates, TemplateQueryReturned(Result<Vec<TemplateEntry>, String>), QueryingFeatures(TemplateEntry), FeaturesQueryReturned(TemplateEntry), UserOptionsSpecifying(TemplateEntry), ConfirmingWriteDevContainer(TemplateEntry), TemplateWriteFailed(DevContainerError), } #[derive(Debug, Clone)] enum DevContainerMessage { SearchTemplates, TemplatesRetrieved(Vec<DevContainerTemplate>), ErrorRetrievingTemplates(String), TemplateSelected(TemplateEntry), TemplateOptionsSpecified(TemplateEntry), TemplateOptionsCompleted(TemplateEntry), FeaturesRetrieved(Vec<DevContainerFeature>), FeaturesSelected(TemplateEntry), NeedConfirmWriteDevContainer(TemplateEntry), ConfirmWriteDevContainer(TemplateEntry), FailedToWriteTemplate(DevContainerError), GoBack, } struct DevContainerModal { workspace: WeakEntity<Workspace>, picker: Option<Entity<Picker<TemplatePickerDelegate>>>, features_picker: Option<Entity<Picker<FeaturePickerDelegate>>>, focus_handle: FocusHandle, confirm_entry: NavigableEntry, back_entry: NavigableEntry, state: DevContainerState, } struct TemplatePickerDelegate { selected_index: usize, placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, candidate_templates: Vec<TemplateEntry>, matching_indices: Vec<usize>, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >, } impl TemplatePickerDelegate { fn new( placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, elements: Vec<TemplateEntry>, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >, ) -> Self { Self { selected_index: 0, placeholder_text, stateful_modal, candidate_templates: elements, matching_indices: Vec::new(), on_confirm, } } } impl PickerDelegate for TemplatePickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.matching_indices.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) { self.selected_index = ix; } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { self.placeholder_text.clone().into() } fn update_matches( &mut self, query: String, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) -> gpui::Task<()> { self.matching_indices = self .candidate_templates .iter() .enumerate() .filter(|(_, template_entry)| { template_entry .template .id .to_lowercase() .contains(&query.to_lowercase()) || template_entry .template .name .to_lowercase() .contains(&query.to_lowercase()) }) .map(|(ix, _)| ix) .collect(); self.selected_index = std::cmp::min( self.selected_index, self.matching_indices.len().saturating_sub(1), ); Task::ready(()) } fn confirm( &mut self, _secondary: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) { let fun = &mut self.on_confirm; if self.matching_indices.is_empty() { return; } self.stateful_modal .update(cx, |modal, cx| { let Some(confirmed_entry) = self .matching_indices .get(self.selected_index) .and_then(|ix| self.candidate_templates.get(*ix)) else { log::error!("Selected index not in range of known matches"); return; }; fun(confirmed_entry.clone(), modal, window, cx); }) .ok(); } fn dismissed(&mut self, window: &mut Window, cx: &mut Context<picker::Picker<Self>>) { self.stateful_modal .update(cx, |modal, cx| { modal.dismiss(&menu::Cancel, window, cx); }) .ok(); } fn render_match( &self, ix: usize, selected: bool, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) -> Option<Self::ListItem> { let Some(template_entry) = self.candidate_templates.get(self.matching_indices[ix]) else { return None; }; Some( ListItem::new("li-template-match") .inset(true) .spacing(ui::ListItemSpacing::Sparse) .start_slot(Icon::new(IconName::Box)) .toggle_state(selected) .child(Label::new(template_entry.template.name.clone())) .into_any_element(), ) } fn render_footer( &self, _window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<AnyElement> { Some( h_flex() .w_full() .p_1p5() .gap_1() .justify_start() .border_t_1() .border_color(cx.theme().colors().border_variant) .child( Button::new("run-action", "Continue") .key_binding( KeyBinding::for_action(&menu::Confirm, cx) .map(|kb| kb.size(rems_from_px(12.))), ) .on_click(|_, window, cx| { window.dispatch_action(menu::Confirm.boxed_clone(), cx) }), ) .into_any_element(), ) } } struct FeaturePickerDelegate { selected_index: usize, placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, candidate_features: Vec<FeatureEntry>, template_entry: TemplateEntry, matching_indices: Vec<usize>, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >, } impl FeaturePickerDelegate { fn new( placeholder_text: String, stateful_modal: WeakEntity<DevContainerModal>, candidate_features: Vec<FeatureEntry>, template_entry: TemplateEntry, on_confirm: Box< dyn FnMut( TemplateEntry, &mut DevContainerModal, &mut Window, &mut Context<DevContainerModal>, ), >, ) -> Self { Self { selected_index: 0, placeholder_text, stateful_modal, candidate_features, template_entry, matching_indices: Vec::new(), on_confirm, } } } impl PickerDelegate for FeaturePickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.matching_indices.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) { self.selected_index = ix; } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { self.placeholder_text.clone().into() } fn update_matches( &mut self, query: String, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Task<()> { self.matching_indices = self .candidate_features .iter() .enumerate() .filter(|(_, feature_entry)| { feature_entry .feature .id .to_lowercase() .contains(&query.to_lowercase()) || feature_entry .feature .name .to_lowercase() .contains(&query.to_lowercase()) }) .map(|(ix, _)| ix) .collect(); self.selected_index = std::cmp::min( self.selected_index, self.matching_indices.len().saturating_sub(1), ); Task::ready(()) } fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) { if secondary { self.stateful_modal .update(cx, |modal, cx| { (self.on_confirm)(self.template_entry.clone(), modal, window, cx) }) .ok(); } else { if self.matching_indices.is_empty() { return; } let Some(current) = self .matching_indices .get(self.selected_index) .and_then(|ix| self.candidate_features.get_mut(*ix)) else { log::error!("Selected index not in range of matches"); return; }; current.toggle_state = match current.toggle_state { ToggleState::Selected => { self.template_entry .features_selected .remove(¤t.feature);