1169 lines · 47.1 KB · Rust
RawDownload
use crate::{    ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack,    StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence,};use crate::{new_session_modal::NewSessionModal, session::DebugSession};use anyhow::{Result, anyhow};use collections::HashMap;use command_palette_hooks::CommandPaletteFilter;use dap::StartDebuggingRequestArguments;use dap::{    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,    client::SessionId, debugger_settings::DebuggerSettings,};use futures::{SinkExt as _, channel::mpsc};use gpui::{    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,    FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,    actions, anchored, deferred,}; use project::debugger::session::{Session, SessionStateEvent};use project::{    Project,    debugger::{        dap_store::{self, DapStore},        session::ThreadStatus,    },    terminals::TerminalKind,};use rpc::proto::{self};use settings::Settings;use std::any::TypeId;use std::path::Path;use std::sync::Arc;use task::{DebugTaskDefinition, DebugTaskTemplate};use terminal_view::terminal_panel::TerminalPanel;use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};use workspace::{    Workspace,    dock::{DockPosition, Panel, PanelEvent},}; pub enum DebugPanelEvent {    Exited(SessionId),    Terminated(SessionId),    Stopped {        client_id: SessionId,        event: StoppedEvent,        go_to_stack_frame: bool,    },    Thread((SessionId, ThreadEvent)),    Continued((SessionId, ContinuedEvent)),    Output((SessionId, OutputEvent)),    Module((SessionId, ModuleEvent)),    LoadedSource((SessionId, LoadedSourceEvent)),    ClientShutdown(SessionId),    CapabilitiesChanged(SessionId),} actions!(debug_panel, [ToggleFocus]);pub struct DebugPanel {    size: Pixels,    sessions: Vec<Entity<DebugSession>>,    active_session: Option<Entity<DebugSession>>,    /// This represents the last debug definition that was created in the new session modal    pub(crate) past_debug_definition: Option<DebugTaskDefinition>,    project: Entity<Project>,    workspace: WeakEntity<Workspace>,    focus_handle: FocusHandle,    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,    _subscriptions: Vec<Subscription>,} impl DebugPanel {    pub fn new(        workspace: &Workspace,        window: &mut Window,        cx: &mut Context<Workspace>,    ) -> Entity<Self> {        cx.new(|cx| {            let project = workspace.project().clone();            let dap_store = project.read(cx).dap_store();             let weak = cx.weak_entity();             let modal_subscription =                cx.observe_new::<tasks_ui::TasksModal>(move |_, window, cx| {                    let modal_entity = cx.entity();                     weak.update(cx, |_: &mut DebugPanel, cx| {                        let Some(window) = window else {                            log::error!("Debug panel couldn't subscribe to tasks modal because there was no window");                            return;                        };                         cx.subscribe_in(                            &modal_entity,                            window,                            |panel, _, event: &tasks_ui::ShowAttachModal, window, cx| {                                panel.workspace.update(cx, |workspace, cx| {                                    let workspace_handle = cx.entity().clone();                                    workspace.toggle_modal(window, cx, |window, cx| {                                        crate::attach_modal::AttachModal::new(                                            workspace_handle,                                            event.debug_config.clone(),                                            true,                                            window,                                            cx,                                        )                                    });                                }).ok();                            },                        )                        .detach();                    })                    .ok();                });             let _subscriptions = vec![                cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event),                modal_subscription,            ];             let debug_panel = Self {                size: px(300.),                sessions: vec![],                active_session: None,                _subscriptions,                past_debug_definition: None,                focus_handle: cx.focus_handle(),                project,                workspace: workspace.weak_handle(),                context_menu: None,            };             debug_panel        })    }     fn filter_action_types(&self, cx: &mut App) {        let (has_active_session, supports_restart, support_step_back, status) = self            .active_session()            .map(|item| {                let running = item.read(cx).mode().as_running().cloned();                 match running {                    Some(running) => {                        let caps = running.read(cx).capabilities(cx);                        (                            !running.read(cx).session().read(cx).is_terminated(),                            caps.supports_restart_request.unwrap_or_default(),                            caps.supports_step_back.unwrap_or_default(),                            running.read(cx).thread_status(cx),                        )                    }                    None => (false, false, false, None),                }            })            .unwrap_or((false, false, false, None));         let filter = CommandPaletteFilter::global_mut(cx);        let debugger_action_types = [            TypeId::of::<Disconnect>(),            TypeId::of::<Stop>(),            TypeId::of::<ToggleIgnoreBreakpoints>(),        ];         let running_action_types = [TypeId::of::<Pause>()];         let stopped_action_type = [            TypeId::of::<Continue>(),            TypeId::of::<StepOver>(),            TypeId::of::<StepInto>(),            TypeId::of::<StepOut>(),            TypeId::of::<editor::actions::DebuggerRunToCursor>(),            TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),        ];         let step_back_action_type = [TypeId::of::<StepBack>()];        let restart_action_type = [TypeId::of::<Restart>()];         if has_active_session {            filter.show_action_types(debugger_action_types.iter());             if supports_restart {                filter.show_action_types(restart_action_type.iter());            } else {                filter.hide_action_types(&restart_action_type);            }             if support_step_back {                filter.show_action_types(step_back_action_type.iter());            } else {                filter.hide_action_types(&step_back_action_type);            }             match status {                Some(ThreadStatus::Running) => {                    filter.show_action_types(running_action_types.iter());                    filter.hide_action_types(&stopped_action_type);                }                Some(ThreadStatus::Stopped) => {                    filter.show_action_types(stopped_action_type.iter());                    filter.hide_action_types(&running_action_types);                }                _ => {                    filter.hide_action_types(&running_action_types);                    filter.hide_action_types(&stopped_action_type);                }            }        } else {            // show only the `debug: start`            filter.hide_action_types(&debugger_action_types);            filter.hide_action_types(&step_back_action_type);            filter.hide_action_types(&restart_action_type);            filter.hide_action_types(&running_action_types);            filter.hide_action_types(&stopped_action_type);        }    }     pub fn load(        workspace: WeakEntity<Workspace>,        cx: &mut AsyncWindowContext,    ) -> Task<Result<Entity<Self>>> {        cx.spawn(async move |cx| {            workspace.update_in(cx, |workspace, window, cx| {                let debug_panel = DebugPanel::new(workspace, window, cx);                 workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {                    workspace.project().read(cx).breakpoint_store().update(                        cx,                        |breakpoint_store, cx| {                            breakpoint_store.clear_breakpoints(cx);                        },                    )                });                 cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {                    Self::filter_action_types(debug_panel, cx);                })                .detach();                 cx.observe(&debug_panel, |_, debug_panel, cx| {                    debug_panel.update(cx, |debug_panel, cx| {                        Self::filter_action_types(debug_panel, cx);                    });                })                .detach();                workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));                 debug_panel            })        })    }     pub fn start_session(        &mut self,        definition: DebugTaskDefinition,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let task_contexts = self            .workspace            .update(cx, |workspace, cx| {                tasks_ui::task_contexts(workspace, window, cx)            })            .ok();        let dap_store = self.project.read(cx).dap_store().clone();         cx.spawn_in(window, async move |this, cx| {            let task_context = if let Some(task) = task_contexts {                task.await                    .active_worktree_context                    .map_or(task::TaskContext::default(), |context| context.1)            } else {                task::TaskContext::default()            };             let (session, task) = dap_store.update(cx, |dap_store, cx| {                let template = DebugTaskTemplate {                    locator: None,                    definition: definition.clone(),                };                let session = if let Some(debug_config) = template                    .to_zed_format()                    .resolve_task("debug_task", &task_context)                    .and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())                {                    dap_store.new_session(debug_config.definition, None, cx)                } else {                    dap_store.new_session(definition.clone(), None, cx)                };                 (session.clone(), dap_store.boot_session(session, cx))            })?;             match task.await {                Err(e) => {                    this.update(cx, |this, cx| {                        this.workspace                            .update(cx, |workspace, cx| {                                workspace.show_error(&e, cx);                            })                            .ok();                    })                    .ok();                     session                        .update(cx, |session, cx| session.shutdown(cx))?                        .await;                }                Ok(_) => Self::register_session(this, session, cx).await?,            }             anyhow::Ok(())        })        .detach_and_log_err(cx);    }     async fn register_session(        this: WeakEntity<Self>,        session: Entity<Session>,        cx: &mut AsyncWindowContext,    ) -> Result<()> {        let adapter_name = session.update(cx, |session, _| session.adapter_name())?;        this.update_in(cx, |_, window, cx| {            cx.subscribe_in(                &session,                window,                move |_, session, event: &SessionStateEvent, window, cx| match event {                    SessionStateEvent::Restart => {                        let mut curr_session = session.clone();                        while let Some(parent_session) = curr_session                            .read_with(cx, |session, _| session.parent_session().cloned())                        {                            curr_session = parent_session;                        }                         let definition = curr_session.update(cx, |session, _| session.definition());                        let task = curr_session.update(cx, |session, cx| session.shutdown(cx));                         let definition = definition.clone();                        cx.spawn_in(window, async move |this, cx| {                            task.await;                             this.update_in(cx, |this, window, cx| {                                this.start_session(definition, window, cx)                            })                        })                        .detach_and_log_err(cx);                    }                    _ => {}                },            )            .detach();        })        .ok();         let serialized_layout = persistence::get_serialized_pane_layout(adapter_name).await;         let workspace = this.update_in(cx, |this, window, cx| {            this.sessions.retain(|session| {                session                    .read(cx)                    .mode()                    .as_running()                    .map_or(false, |running_state| {                        !running_state.read(cx).session().read(cx).is_terminated()                    })            });             let session_item = DebugSession::running(                this.project.clone(),                this.workspace.clone(),                session,                cx.weak_entity(),                serialized_layout,                window,                cx,            );             if let Some(running) = session_item.read(cx).mode().as_running().cloned() {                // We might want to make this an event subscription and only notify when a new thread is selected                // This is used to filter the command menu correctly                cx.observe(&running, |_, _, cx| cx.notify()).detach();            }             this.sessions.push(session_item.clone());            this.activate_session(session_item, window, cx);            this.workspace.clone()        })?;         workspace.update_in(cx, |workspace, window, cx| {            workspace.focus_panel::<Self>(window, cx);        })?;        Ok(())    }     pub fn start_child_session(        &mut self,        request: &StartDebuggingRequestArguments,        parent_session: Entity<Session>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let Some(worktree) = parent_session.read(cx).worktree() else {            log::error!("Attempted to start a child session from non local debug session");            return;        };         let dap_store_handle = self.project.read(cx).dap_store().clone();        let breakpoint_store = self.project.read(cx).breakpoint_store();        let definition = parent_session.read(cx).definition().clone();        let mut binary = parent_session.read(cx).binary().clone();        binary.request_args = request.clone();         cx.spawn_in(window, async move |this, cx| {            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {                let session =                    dap_store.new_session(definition.clone(), Some(parent_session.clone()), cx);                 let task = session.update(cx, |session, cx| {                    session.boot(                        binary,                        worktree,                        breakpoint_store,                        dap_store_handle.downgrade(),                        cx,                    )                });                (session, task)            })?;             match task.await {                Err(e) => {                    this.update(cx, |this, cx| {                        this.workspace                            .update(cx, |workspace, cx| {                                workspace.show_error(&e, cx);                            })                            .ok();                    })                    .ok();                     session                        .update(cx, |session, cx| session.shutdown(cx))?                        .await;                }                Ok(_) => Self::register_session(this, session, cx).await?,            }             anyhow::Ok(())        })        .detach_and_log_err(cx);    }     pub fn active_session(&self) -> Option<Entity<DebugSession>> {        self.active_session.clone()    }     fn handle_dap_store_event(        &mut self,        _dap_store: &Entity<DapStore>,        event: &dap_store::DapStoreEvent,        window: &mut Window,        cx: &mut Context<Self>,    ) {        match event {            dap_store::DapStoreEvent::RunInTerminal {                title,                cwd,                command,                args,                envs,                sender,                ..            } => {                self.handle_run_in_terminal_request(                    title.clone(),                    cwd.clone(),                    command.clone(),                    args.clone(),                    envs.clone(),                    sender.clone(),                    window,                    cx,                )                .detach_and_log_err(cx);            }            dap_store::DapStoreEvent::SpawnChildSession {                request,                parent_session,            } => {                self.start_child_session(request, parent_session.clone(), window, cx);            }            _ => {}        }    }     fn handle_run_in_terminal_request(

Lines 1–500 of 1169.