1288 lines · 52.7 KB · Rust
RawDownload
use crate::persistence::DebuggerPaneItem;use crate::{    ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, FocusBreakpointList,    FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,    Pause, Restart, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,    persistence,};use crate::{new_session_modal::NewSessionModal, session::DebugSession};use anyhow::{Result, anyhow};use command_palette_hooks::CommandPaletteFilter;use dap::DebugRequest;use dap::{    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,    client::SessionId, debugger_settings::DebuggerSettings,};use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};use gpui::{    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,    FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,    actions, anchored, deferred,}; use language::Buffer;use project::debugger::session::{Session, SessionStateEvent};use project::{Project, debugger::session::ThreadStatus};use rpc::proto::{self};use settings::Settings;use std::any::TypeId;use task::{DebugScenario, TaskContext};use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};use workspace::SplitDirection;use workspace::{    Pane, 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)>,} 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 debug_panel = Self {                size: px(300.),                sessions: vec![],                active_session: None,                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).running_state().clone();                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),                )            })            .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            })        })    }     fn start_from_definition(        &mut self,        definition: DebugTaskDefinition,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Task<Result<()>> {        cx.spawn_in(window, async move |this, cx| {            let dap_store = this.update(cx, |this, cx| this.project.read(cx).dap_store())?;            let (session, task) = dap_store.update(cx, |dap_store, cx| {                let session = dap_store.new_session(definition, None, cx);                 (session.clone(), dap_store.boot_session(session, cx))            })?;            Self::register_session(this.clone(), session.clone(), cx).await?;             if let Err(e) = task.await {                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;            }             anyhow::Ok(())        })    }     pub fn start_session(        &mut self,        scenario: DebugScenario,        task_context: TaskContext,        active_buffer: Option<Entity<Buffer>>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        cx.spawn_in(window, async move |this, cx| {            let definition = this                .update_in(cx, |this, window, cx| {                    this.resolve_scenario(scenario, task_context, active_buffer, window, cx)                })?                .await?;            this.update_in(cx, |this, window, cx| {                this.start_from_definition(definition, window, cx)            })?            .await        })        .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 |this, 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));                         cx.spawn_in(window, async move |this, cx| {                            task.await;                             this.update_in(cx, |this, window, cx| {                                this.start_from_definition(definition, window, cx)                            })?                            .await                        })                        .detach_and_log_err(cx);                    }                    SessionStateEvent::SpawnChildSession { request } => {                        this.handle_start_debugging_request(request, session.clone(), window, 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)                    .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,            );             // 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(                &session_item.read(cx).running_state().clone(),                |_, _, 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 handle_start_debugging_request(        &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()    }     pub fn resolve_scenario(        &self,        scenario: DebugScenario,         task_context: TaskContext,        buffer: Option<Entity<Buffer>>,        window: &Window,        cx: &mut Context<Self>,    ) -> Task<Result<DebugTaskDefinition>> {        let project = self.project.read(cx);        let dap_store = project.dap_store().downgrade();        let task_store = project.task_store().downgrade();        let workspace = self.workspace.clone();        cx.spawn_in(window, async move |_, cx| {            let DebugScenario {                adapter,                label,                build,                request,                initialize_args,                tcp_connection,                stop_on_entry,            } = scenario;            let request = if let Some(mut request) = request {                // Resolve task variables within the request.                if let DebugRequest::Launch(_) = &mut request {}                 request            } else if let Some(build) = build {                let Some(task) = task_store.update(cx, |this, cx| {                    this.task_inventory().and_then(|inventory| {                        inventory                            .read(cx)                            .task_template_by_label(buffer, &build, cx)                    })                })?                else {                    anyhow::bail!("Couldn't find task template for {:?}", build)                };                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {                    anyhow::bail!("Could not resolve task variables within a debug scenario");                };                 let run_build = workspace.update_in(cx, |workspace, window, cx| {                    workspace.spawn_in_terminal(task.resolved.clone(), window, cx)                })?;                 let exit_status = run_build.await?;                if !exit_status.success() {                    anyhow::bail!("Build failed");                }                 dap_store                    .update(cx, |this, cx| this.run_debug_locator(task.resolved, cx))?                    .await?            } else {                return Err(anyhow!("No request or build provided"));            };            Ok(DebugTaskDefinition {                label,                adapter,                request,                initialize_args,                stop_on_entry,                tcp_connection,            })        })    }     fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {        let Some(session) = self            .sessions            .iter()            .find(|other| entity_id == other.entity_id())            .cloned()        else {            return;        };        session.update(cx, |this, cx| {            this.running_state().update(cx, |this, cx| {                this.serialize_layout(window, cx);            });        });        let session_id = session.update(cx, |this, cx| this.session_id(cx));        let should_prompt = self            .project            .update(cx, |this, cx| {                let session = this.dap_store().read(cx).session_by_id(session_id);                session.map(|session| !session.read(cx).is_terminated())            })            .unwrap_or_default();         cx.spawn_in(window, async move |this, cx| {            if should_prompt {                let response = cx.prompt(                    gpui::PromptLevel::Warning,                    "This Debug Session is still running. Are you sure you want to terminate it?",                    None,

Lines 1–500 of 1288.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t