1886 lines · 66.6 KB · Rust
RawDownload
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(&current.feature);

Lines 1–500 of 1886.