1327 lines · 48.8 KB · Rust
RawDownload
use collections::FxHashMap;use language::LanguageRegistry;use paths::local_debug_file_relative_path;use std::{    borrow::Cow,    path::{Path, PathBuf},    sync::Arc,    usize,};use tasks_ui::{TaskOverrides, TasksModal}; use dap::{    DapRegistry, DebugRequest, TelemetrySpawnLocation, adapters::DebugAdapterName, send_telemetry,};use editor::{Editor, EditorElement, EditorStyle};use fuzzy::{StringMatch, StringMatchCandidate};use gpui::{    App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle,    InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText, Subscription,    TextStyle, UnderlineStyle, WeakEntity,};use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore};use settings::{Settings, initial_local_debug_tasks_content};use task::{DebugScenario, RevealTarget, ZedDebugConfig};use theme::ThemeSettings;use ui::{    ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,    ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconName, IconSize,    IconWithIndicator, Indicator, InteractiveElement, IntoElement, Label, LabelCommon as _,    ListItem, ListItemSpacing, ParentElement, RenderOnce, SharedString, Styled, StyledExt,    StyledTypography, ToggleButton, ToggleState, Toggleable, Tooltip, Window, div, h_flex, px,    relative, rems, v_flex,};use util::ResultExt;use workspace::{ModalView, Workspace, pane}; use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel}; // enum SaveScenarioState {//     Saving,//     Saved((ProjectPath, SharedString)),//     Failed(SharedString),// } pub(super) struct NewProcessModal {    workspace: WeakEntity<Workspace>,    debug_panel: WeakEntity<DebugPanel>,    mode: NewProcessMode,    debug_picker: Entity<Picker<DebugDelegate>>,    attach_mode: Entity<AttachMode>,    launch_mode: Entity<LaunchMode>,    task_mode: TaskMode,    debugger: Option<DebugAdapterName>,    // save_scenario_state: Option<SaveScenarioState>,    _subscriptions: [Subscription; 3],} fn suggested_label(request: &DebugRequest, debugger: &str) -> SharedString {    match request {        DebugRequest::Launch(config) => {            let last_path_component = Path::new(&config.program)                .file_name()                .map(|name| name.to_string_lossy())                .unwrap_or_else(|| Cow::Borrowed(&config.program));             format!("{} ({debugger})", last_path_component).into()        }        DebugRequest::Attach(config) => format!(            "pid: {} ({debugger})",            config.process_id.unwrap_or(u32::MAX)        )        .into(),    }} impl NewProcessModal {    pub(super) fn show(        workspace: &mut Workspace,        window: &mut Window,        mode: NewProcessMode,        reveal_target: Option<RevealTarget>,        cx: &mut Context<Workspace>,    ) {        let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {            return;        };        let task_store = workspace.project().read(cx).task_store().clone();        let languages = workspace.app_state().languages.clone();         cx.spawn_in(window, async move |workspace, cx| {            let task_contexts = workspace.update_in(cx, |workspace, window, cx| {                tasks_ui::task_contexts(workspace, window, cx)            })?;            workspace.update_in(cx, |workspace, window, cx| {                let workspace_handle = workspace.weak_handle();                workspace.toggle_modal(window, cx, |window, cx| {                    let attach_mode = AttachMode::new(None, workspace_handle.clone(), window, cx);                     let launch_picker = cx.new(|cx| {                        let delegate =                            DebugDelegate::new(debug_panel.downgrade(), task_store.clone());                        Picker::uniform_list(delegate, window, cx).modal(false)                    });                     let configure_mode = LaunchMode::new(window, cx);                     let task_overrides = Some(TaskOverrides { reveal_target });                     let task_mode = TaskMode {                        task_modal: cx.new(|cx| {                            TasksModal::new(                                task_store.clone(),                                Arc::new(TaskContexts::default()),                                task_overrides,                                false,                                workspace_handle.clone(),                                window,                                cx,                            )                        }),                    };                     let _subscriptions = [                        cx.subscribe(&launch_picker, |_, _, _, cx| {                            cx.emit(DismissEvent);                        }),                        cx.subscribe(                            &attach_mode.read(cx).attach_picker.clone(),                            |_, _, _, cx| {                                cx.emit(DismissEvent);                            },                        ),                        cx.subscribe(&task_mode.task_modal, |_, _, _: &DismissEvent, cx| {                            cx.emit(DismissEvent)                        }),                    ];                     cx.spawn_in(window, {                        let launch_picker = launch_picker.downgrade();                        let configure_mode = configure_mode.downgrade();                        let task_modal = task_mode.task_modal.downgrade();                         async move |this, cx| {                            let task_contexts = task_contexts.await;                            let task_contexts = Arc::new(task_contexts);                            launch_picker                                .update_in(cx, |picker, window, cx| {                                    picker.delegate.task_contexts_loaded(                                        task_contexts.clone(),                                        languages,                                        window,                                        cx,                                    );                                    picker.refresh(window, cx);                                    cx.notify();                                })                                .ok();                             if let Some(active_cwd) = task_contexts                                .active_context()                                .and_then(|context| context.cwd.clone())                            {                                configure_mode                                    .update_in(cx, |configure_mode, window, cx| {                                        configure_mode.load(active_cwd, window, cx);                                    })                                    .ok();                            }                             task_modal                                .update_in(cx, |task_modal, window, cx| {                                    task_modal.task_contexts_loaded(task_contexts, window, cx);                                })                                .ok();                             this.update(cx, |_, cx| {                                cx.notify();                            })                            .ok();                        }                    })                    .detach();                     Self {                        debug_picker: launch_picker,                        attach_mode,                        launch_mode: configure_mode,                        task_mode,                        debugger: None,                        mode,                        debug_panel: debug_panel.downgrade(),                        workspace: workspace_handle,                        // save_scenario_state: None,                        _subscriptions,                    }                });            })?;             anyhow::Ok(())        })        .detach();    }     fn render_mode(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {        let dap_menu = self.adapter_drop_down_menu(window, cx);        match self.mode {            NewProcessMode::Task => self                .task_mode                .task_modal                .read(cx)                .picker                .clone()                .into_any_element(),            NewProcessMode::Attach => self.attach_mode.update(cx, |this, cx| {                this.clone().render(window, cx).into_any_element()            }),            NewProcessMode::Launch => self.launch_mode.update(cx, |this, cx| {                this.clone().render(dap_menu, window, cx).into_any_element()            }),            NewProcessMode::Debug => v_flex()                .w(rems(34.))                .child(self.debug_picker.clone())                .into_any_element(),        }    }     fn mode_focus_handle(&self, cx: &App) -> FocusHandle {        match self.mode {            NewProcessMode::Task => self.task_mode.task_modal.focus_handle(cx),            NewProcessMode::Attach => self.attach_mode.read(cx).attach_picker.focus_handle(cx),            NewProcessMode::Launch => self.launch_mode.read(cx).program.focus_handle(cx),            NewProcessMode::Debug => self.debug_picker.focus_handle(cx),        }    }     fn debug_scenario(&self, debugger: &str, cx: &App) -> Option<DebugScenario> {        let request = match self.mode {            NewProcessMode::Launch => Some(DebugRequest::Launch(                self.launch_mode.read(cx).debug_request(cx),            )),            NewProcessMode::Attach => Some(DebugRequest::Attach(                self.attach_mode.read(cx).debug_request(),            )),            _ => None,        }?;        let label = suggested_label(&request, debugger);         let stop_on_entry = if let NewProcessMode::Launch = &self.mode {            Some(self.launch_mode.read(cx).stop_on_entry.selected())        } else {            None        };         let session_scenario = ZedDebugConfig {            adapter: debugger.to_owned().into(),            label,            request: request,            stop_on_entry,        };         cx.global::<DapRegistry>()            .adapter(&session_scenario.adapter)            .and_then(|adapter| adapter.config_from_zed_format(session_scenario).ok())    }     fn start_new_session(&mut self, window: &mut Window, cx: &mut Context<Self>) {        if self.debugger.as_ref().is_none() {            return;        }         if let NewProcessMode::Debug = &self.mode {            self.debug_picker.update(cx, |picker, cx| {                picker.delegate.confirm(false, window, cx);            });            return;        }         // TODO: Restore once we have proper, comment preserving edits        // if let NewProcessMode::Launch = &self.mode {        //     if self.launch_mode.read(cx).save_to_debug_json.selected() {        //         self.save_debug_scenario(window, cx);        //     }        // }         let Some(debugger) = self.debugger.as_ref() else {            return;        };         let Some(config) = self.debug_scenario(debugger, cx) else {            log::error!("debug config not found in mode: {}", self.mode);            return;        };         let debug_panel = self.debug_panel.clone();        let Some(task_contexts) = self.task_contexts(cx) else {            return;        };        send_telemetry(&config, TelemetrySpawnLocation::Custom, cx);        let task_context = task_contexts.active_context().cloned().unwrap_or_default();        let worktree_id = task_contexts.worktree();        cx.spawn_in(window, async move |this, cx| {            debug_panel.update_in(cx, |debug_panel, window, cx| {                debug_panel.start_session(config, task_context, None, worktree_id, window, cx)            })?;            this.update(cx, |_, cx| {                cx.emit(DismissEvent);            })            .ok();            anyhow::Ok(())        })        .detach_and_log_err(cx);    }     fn update_attach_picker(        attach: &Entity<AttachMode>,        adapter: &DebugAdapterName,        window: &mut Window,        cx: &mut App,    ) {        attach.update(cx, |this, cx| {            if adapter.0 != this.definition.adapter {                this.definition.adapter = adapter.0.clone();                 this.attach_picker.update(cx, |this, cx| {                    this.picker.update(cx, |this, cx| {                        this.delegate.definition.adapter = adapter.0.clone();                        this.focus(window, cx);                    })                });            }             cx.notify();        })    }     fn task_contexts(&self, cx: &App) -> Option<Arc<TaskContexts>> {        self.debug_picker.read(cx).delegate.task_contexts.clone()    }     // fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {    //     let Some((save_scenario, scenario_label)) = self    //         .debugger    //         .as_ref()    //         .and_then(|debugger| self.debug_scenario(&debugger, cx))    //         .zip(self.task_contexts(cx).and_then(|tcx| tcx.worktree()))    //         .and_then(|(scenario, worktree_id)| {    //             self.debug_panel    //                 .update(cx, |panel, cx| {    //                     panel.save_scenario(&scenario, worktree_id, window, cx)    //                 })    //                 .ok()    //                 .zip(Some(scenario.label.clone()))    //         })    //     else {    //         return;    //     };     //     self.save_scenario_state = Some(SaveScenarioState::Saving);     //     cx.spawn(async move |this, cx| {    //         let res = save_scenario.await;     //         this.update(cx, |this, _| match res {    //             Ok(saved_file) => {    //                 this.save_scenario_state =    //                     Some(SaveScenarioState::Saved((saved_file, scenario_label)))    //             }    //             Err(error) => {    //                 this.save_scenario_state =    //                     Some(SaveScenarioState::Failed(error.to_string().into()))    //             }    //         })    //         .ok();     //         cx.background_executor().timer(Duration::from_secs(3)).await;    //         this.update(cx, |this, _| this.save_scenario_state.take())    //             .ok();    //     })    //     .detach();    // }     fn adapter_drop_down_menu(        &mut self,        window: &mut Window,        cx: &mut Context<Self>,    ) -> ui::DropdownMenu {        let workspace = self.workspace.clone();        let weak = cx.weak_entity();        let active_buffer = self.task_contexts(cx).and_then(|tc| {            tc.active_item_context                .as_ref()                .and_then(|aic| aic.1.as_ref().map(|l| l.buffer.clone()))        });         let active_buffer_language = active_buffer            .and_then(|buffer| buffer.read(cx).language())            .cloned();         let mut available_adapters = workspace            .update(cx, |_, cx| DapRegistry::global(cx).enumerate_adapters())            .unwrap_or_default();        if let Some(language) = active_buffer_language {            available_adapters.sort_by_key(|adapter| {                language                    .config()                    .debuggers                    .get_index_of(adapter.0.as_ref())                    .unwrap_or(usize::MAX)            });        }         if self.debugger.is_none() {            self.debugger = available_adapters.first().cloned();        }         let label = self            .debugger            .as_ref()            .map(|d| d.0.clone())            .unwrap_or_else(|| SELECT_DEBUGGER_LABEL.clone());         DropdownMenu::new(            "dap-adapter-picker",            label,            ContextMenu::build(window, cx, move |mut menu, _, _| {                let setter_for_name = |name: DebugAdapterName| {                    let weak = weak.clone();                    move |window: &mut Window, cx: &mut App| {                        weak.update(cx, |this, cx| {                            this.debugger = Some(name.clone());                            cx.notify();                            if let NewProcessMode::Attach = &this.mode {                                Self::update_attach_picker(&this.attach_mode, &name, window, cx);                            }                        })                        .ok();                    }                };                 for adapter in available_adapters.into_iter() {                    menu = menu.entry(adapter.0.clone(), None, setter_for_name(adapter.clone()));                }                 menu            }),        )    }     fn open_debug_json(&self, window: &mut Window, cx: &mut Context<NewProcessModal>) {        let this = cx.entity();        window            .spawn(cx, async move |cx| {                let worktree_id = this.update(cx, |this, cx| {                    let tcx = this.task_contexts(cx);                    tcx?.worktree()                })?;                 let Some(worktree_id) = worktree_id else {                    let _ = cx.prompt(                        PromptLevel::Critical,                        "Cannot open debug.json",                        Some("You must have at least one project open"),                        &[PromptButton::ok("Ok")],                    );                    return Ok(());                };                 let editor = this                    .update_in(cx, |this, window, cx| {                        this.workspace.update(cx, |workspace, cx| {                            workspace.open_path(                                ProjectPath {                                    worktree_id,                                    path: local_debug_file_relative_path().into(),                                },                                None,                                true,                                window,                                cx,                            )                        })                    })??                    .await?;                 cx.update(|_window, cx| {                    if let Some(editor) = editor.act_as::<Editor>(cx) {                        editor.update(cx, |editor, cx| {                            editor.buffer().update(cx, |buffer, cx| {                                if let Some(singleton) = buffer.as_singleton() {                                    singleton.update(cx, |buffer, cx| {                                        if buffer.is_empty() {                                            buffer.edit(                                                [(0..0, initial_local_debug_tasks_content())],                                                None,                                                cx,                                            );                                        }                                    })                                }

Lines 1–500 of 1327.