1583 lines · 58.6 KB · Rust
RawDownload
use anyhow::bail;use collections::{FxHashMap, HashMap};use language::LanguageRegistry;use paths::local_debug_file_relative_path;use std::{    borrow::Cow,    path::{Path, PathBuf},    sync::Arc,    time::Duration,    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::{    Action, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,    HighlightStyle, InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText,    Subscription, Task, TextStyle, UnderlineStyle, WeakEntity,};use itertools::Itertools as _;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, CheckboxWithLabel, Clickable, Context, ContextMenu, Disableable, DropdownMenu,    FluentBuilder, IconWithIndicator, Indicator, IntoElement, KeyBinding, ListItem,    ListItemSpacing, ParentElement, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip,    Window, div, prelude::*, px, relative, rems,};use util::ResultExt;use workspace::{ModalView, Workspace, pane}; use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel}; #[allow(unused)]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>,    configure_mode: Entity<ConfigureMode>,    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 debug_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 = 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,                        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.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 {            if 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();        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| {                        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 task_contents = self.task_contexts(cx);        let Some(adapter) = self.debugger.as_ref() else {            return;        };        let scenario = self.debug_scenario(&adapter, cx);         self.save_scenario_state = Some(SaveScenarioState::Saving);         cx.spawn_in(window, async move |this, cx| {            let Some((scenario, worktree_id)) = scenario                .await                .zip(task_contents.and_then(|tcx| tcx.worktree()))            else {                this.update(cx, |this, _| {                    this.save_scenario_state = Some(SaveScenarioState::Failed(                        "Couldn't get scenario or task contents".into(),                    ))                })                .ok();                return;            };             let Some(save_scenario) = this                .update_in(cx, |this, window, cx| {                    this.debug_panel                        .update(cx, |panel, cx| {                            panel.save_scenario(&scenario, worktree_id, window, cx)                        })                        .ok()                })                .ok()                .flatten()            else {                return;            };            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.clone(),                    )))                }                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| {

Lines 1–500 of 1583.