1669 lines · 62.0 KB · Rust
RawDownload
use anyhow::{Context as _, bail};use collections::{FxHashMap, HashMap, HashSet};use language::{LanguageName, LanguageRegistry};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;use fuzzy::{StringMatch, StringMatchCandidate};use gpui::{    Action, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,    KeyContext, Render, Subscription, Task, TaskExt, WeakEntity, actions,};use itertools::Itertools as _;use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};use project::{DebugScenarioContext, Project, TaskContexts, TaskSourceKind, task_store::TaskStore};use task::{DebugScenario, RevealTarget, SharedTaskContext, VariableName, ZedDebugConfig};use ui::{    ContextMenu, DropdownMenu, IconWithIndicator, Indicator, KeyBinding, ListItem, ListItemSpacing,    Switch, SwitchLabelPosition, ToggleButtonGroup, ToggleButtonSimple, ToggleState, Tooltip,    prelude::*,};use ui_input::InputField;use util::{ResultExt, debug_panic, rel_path::RelPath, shell::ShellKind};use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr, pane}; use crate::{    attach_modal::{AttachModal, ModalIntent},    debugger_panel::DebugPanel,}; actions!(    new_process_modal,    [        ActivateTaskTab,        ActivateDebugTab,        ActivateAttachTab,        ActivateLaunchTab    ]); pub(super) struct NewProcessModal {    workspace: WeakEntity<Workspace>,    debug_panel: WeakEntity<DebugPanel>,    mode: NewProcessMode,    debug_picker: Entity<Picker<DebugDelegate>>,    attach_mode: Entity<AttachMode>,    configure_mode: Entity<ConfigureMode>,    task_mode: TaskMode,    debugger: Option<DebugAdapterName>,    _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| {                // todo(debugger): get the buffer here (if the active item is an editor) and store it so we can pass it to start_session later                tasks_ui::task_contexts(workspace, window, cx)            })?;            workspace.update_in(cx, |workspace, window, cx| {                let workspace_handle = workspace.weak_handle();                let project = workspace.project().clone();                workspace.toggle_modal(window, cx, |window, cx| {                    let attach_mode =                        AttachMode::new(None, workspace_handle.clone(), project, window, cx);                     let debug_picker = cx.new(|cx| {                        let delegate =                            DebugDelegate::new(debug_panel.downgrade(), task_store.clone());                        Picker::list(delegate, window, cx)                            .embedded()                            .list_measure_all()                    });                     let configure_mode = ConfigureMode::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(&debug_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 debug_picker = debug_picker.downgrade();                        let configure_mode = configure_mode.downgrade();                        let task_modal = task_mode.task_modal.downgrade();                        let workspace = workspace_handle.clone();                         async move |this, cx| {                            let task_contexts = task_contexts.await;                            let task_contexts = Arc::new(task_contexts);                            let lsp_task_sources = task_contexts.lsp_task_sources.clone();                            let task_position = task_contexts.latest_selection;                            // Get LSP tasks and filter out based on language vs lsp preference                            let (lsp_tasks, prefer_lsp) =                                workspace.update(cx, |workspace, cx| {                                    let lsp_tasks = editor::lsp_tasks(                                        workspace.project().clone(),                                        &lsp_task_sources,                                        task_position,                                        cx,                                    );                                    let prefer_lsp = workspace                                        .active_item(cx)                                        .and_then(|item| item.downcast::<Editor>())                                        .map(|editor| {                                            editor                                                .read(cx)                                                .buffer()                                                .read(cx)                                                .language_settings(cx)                                                .tasks                                                .prefer_lsp                                        })                                        .unwrap_or(false);                                    (lsp_tasks, prefer_lsp)                                })?;                             let lsp_tasks = lsp_tasks.await;                            let add_current_language_tasks = !prefer_lsp || lsp_tasks.is_empty();                             let lsp_tasks = lsp_tasks                                .into_iter()                                .flat_map(|(kind, tasks_with_locations)| {                                    tasks_with_locations                                        .into_iter()                                        .sorted_by_key(|(location, task)| {                                            (location.is_none(), task.resolved_label.clone())                                        })                                        .map(move |(_, task)| (kind.clone(), task))                                })                                .collect::<Vec<_>>();                             let Some(task_inventory) = task_store                                .update(cx, |task_store, _| task_store.task_inventory().cloned())                            else {                                return Ok(());                            };                             let (used_tasks, current_resolved_tasks) = task_inventory                                .update(cx, |task_inventory, cx| {                                    task_inventory                                        .used_and_current_resolved_tasks(task_contexts.clone(), cx)                                })                                .await;                             if let Ok(task) = debug_picker.update(cx, |picker, cx| {                                picker.delegate.tasks_loaded(                                    task_contexts.clone(),                                    languages,                                    lsp_tasks.clone(),                                    current_resolved_tasks.clone(),                                    add_current_language_tasks,                                    cx,                                )                            }) {                                task.await;                                debug_picker                                    .update_in(cx, |picker, 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.tasks_loaded(                                        task_contexts,                                        lsp_tasks,                                        used_tasks,                                        current_resolved_tasks,                                        add_current_language_tasks,                                        window,                                        cx,                                    );                                })                                .ok();                             this.update(cx, |_, cx| {                                cx.notify();                            })                            .ok();                             anyhow::Ok(())                        }                    })                    .detach();                     Self {                        debug_picker,                        attach_mode,                        configure_mode,                        task_mode,                        debugger: None,                        mode,                        debug_panel: debug_panel.downgrade(),                        workspace: workspace_handle,                        _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.configure_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.configure_mode.read(cx).program.focus_handle(cx),            NewProcessMode::Debug => self.debug_picker.focus_handle(cx),        }    }     fn debug_scenario(&self, debugger: &str, cx: &App) -> Task<Option<DebugScenario>> {        let request = match self.mode {            NewProcessMode::Launch => {                DebugRequest::Launch(self.configure_mode.read(cx).debug_request(cx))            }            NewProcessMode::Attach => {                DebugRequest::Attach(self.attach_mode.read(cx).debug_request())            }            _ => return Task::ready(None),        };        let label = suggested_label(&request, debugger);         let stop_on_entry = if let NewProcessMode::Launch = &self.mode {            Some(self.configure_mode.read(cx).stop_on_entry.selected())        } else {            None        };         let session_scenario = ZedDebugConfig {            adapter: debugger.to_owned().into(),            label,            request,            stop_on_entry,        };         let adapter = cx            .global::<DapRegistry>()            .adapter(&session_scenario.adapter);         cx.spawn(async move |_| adapter?.config_from_zed_format(session_scenario).await.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;        }         if let NewProcessMode::Launch = &self.mode            && self.configure_mode.read(cx).save_to_debug_json.selected()        {            self.save_debug_scenario(window, cx);        }         let Some(debugger) = self.debugger.clone() else {            return;        };         let debug_panel = self.debug_panel.clone();        let Some(task_contexts) = self.task_contexts(cx) else {            return;        };         let task_context = task_contexts            .active_context()            .cloned()            .unwrap_or_default()            .into();        let worktree_id = task_contexts.worktree();        let mode = self.mode;        cx.spawn_in(window, async move |this, cx| {            let Some(config) = this                .update(cx, |this, cx| this.debug_scenario(&debugger, cx))?                .await            else {                bail!("debug config not found in mode: {mode}");            };             debug_panel.update_in(cx, |debug_panel, window, cx| {                send_telemetry(&config, TelemetrySpawnLocation::Custom, 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| {                        match &mut this.delegate.intent {                            ModalIntent::AttachToProcess(definition) => {                                definition.adapter = adapter.0.clone();                                this.focus(window, cx);                            },                            ModalIntent::ResolveProcessId(_) => {                                debug_panic!("Attach picker attempted to update config when in resolve Process ID mode");                            }                        }                    })                });            }             cx.notify();        })    }     fn task_contexts(&self, cx: &App) -> Option<Arc<TaskContexts>> {        self.debug_picker.read(cx).delegate.task_contexts.clone()    }     pub fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {        let task_contexts = self.task_contexts(cx);        let Some(adapter) = self.debugger.as_ref() else {            return;        };        let scenario = self.debug_scenario(adapter, cx);        cx.spawn_in(window, async move |this, cx| {            let scenario = scenario.await.context("no scenario to save")?;            let worktree_id = task_contexts                .context("no task contexts")?                .worktree()                .context("no active worktree")?;            this.update_in(cx, |this, window, cx| {                this.debug_panel.update(cx, |panel, cx| {                    panel.save_scenario(scenario, worktree_id, window, cx)                })            })??            .await?;            this.update_in(cx, |_, _, cx| {                cx.emit(DismissEvent);            })        })        .detach_and_prompt_err("Failed to edit debug.json", window, cx, |_, _, _| None);    }     fn adapter_drop_down_menu(        &mut self,        window: &mut Window,        cx: &mut Context<Self>,    ) -> 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: Vec<_> = 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",

Lines 1–500 of 1669.