2009 lines · 81.3 KB · Rust
RawDownload
use crate::persistence::DebuggerPaneItem;use crate::session::DebugSession;use crate::session::running::RunningState;use crate::session::running::breakpoint_list::BreakpointList; use crate::{    ClearAllBreakpoints, Continue, CopyDebugAdapterArguments, Detach, FocusBreakpointList,    FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,    NewProcessModal, NewProcessMode, Pause, RerunSession, StepInto, StepOut, StepOver, Stop,    ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,};use anyhow::{Context as _, Result, anyhow};use collections::IndexMap;use dap::adapters::DebugAdapterName;use dap::{DapRegistry, StartDebuggingRequestArguments};use dap::{client::SessionId, debugger_settings::DebuggerSettings};use editor::{Editor, MultiBufferOffset, ToPoint};use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};use gpui::{    Action, App, AsyncWindowContext, ClipboardItem, Context, Corner, DismissEvent, Entity,    EntityId, EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point,    Subscription, Task, WeakEntity, anchored, deferred,}; use itertools::Itertools as _;use language::Buffer;use project::debugger::session::{Session, SessionQuirks, SessionState, SessionStateEvent};use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, WorktreeId};use project::{Project, debugger::session::ThreadStatus};use rpc::proto::{self};use settings::Settings;use std::sync::{Arc, LazyLock};use task::{DebugScenario, SharedTaskContext};use tree_sitter::{Query, StreamingIterator as _};use ui::{    ContextMenu, Divider, PopoverMenu, PopoverMenuHandle, SplitButton, Tab, Tooltip, prelude::*,};use util::redact::redact_command;use util::rel_path::RelPath;use util::{ResultExt, debug_panic, maybe};use workspace::SplitDirection;use workspace::item::SaveOptions;use workspace::{    Item, Pane, Workspace,    dock::{DockPosition, Panel, PanelEvent},};use zed_actions::debug_panel::ToggleFocus; pub struct DebuggerHistoryFeatureFlag; impl FeatureFlag for DebuggerHistoryFeatureFlag {    const NAME: &'static str = "debugger-history";} const DEBUG_PANEL_KEY: &str = "DebugPanel"; pub struct DebugPanel {    size: Pixels,    active_session: Option<Entity<DebugSession>>,    project: Entity<Project>,    workspace: WeakEntity<Workspace>,    focus_handle: FocusHandle,    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,    debug_scenario_scheduled_last: bool,    pub(crate) sessions_with_children:        IndexMap<Entity<DebugSession>, Vec<WeakEntity<DebugSession>>>,    pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,    pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,    fs: Arc<dyn Fs>,    is_zoomed: bool,    _subscriptions: [Subscription; 1],    breakpoint_list: Entity<BreakpointList>,} 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 focus_handle = cx.focus_handle();            let thread_picker_menu_handle = PopoverMenuHandle::default();            let session_picker_menu_handle = PopoverMenuHandle::default();             let focus_subscription = cx.on_focus(                &focus_handle,                window,                |this: &mut DebugPanel, window, cx| {                    this.focus_active_item(window, cx);                },            );             Self {                size: px(300.),                sessions_with_children: Default::default(),                active_session: None,                focus_handle,                breakpoint_list: BreakpointList::new(                    None,                    workspace.weak_handle(),                    &project,                    window,                    cx,                ),                project,                workspace: workspace.weak_handle(),                context_menu: None,                fs: workspace.app_state().fs.clone(),                thread_picker_menu_handle,                session_picker_menu_handle,                is_zoomed: false,                _subscriptions: [focus_subscription],                debug_scenario_scheduled_last: true,            }        })    }     pub(crate) fn focus_active_item(&mut self, window: &mut Window, cx: &mut Context<Self>) {        let Some(session) = self.active_session.clone() else {            return;        };        let active_pane = session            .read(cx)            .running_state()            .read(cx)            .active_pane()            .clone();        active_pane.update(cx, |pane, cx| {            pane.focus_active_item(window, cx);        });    }     #[cfg(test)]    pub(crate) fn sessions(&self) -> impl Iterator<Item = Entity<DebugSession>> {        self.sessions_with_children.keys().cloned()    }     pub fn active_session(&self) -> Option<Entity<DebugSession>> {        self.active_session.clone()    }     pub(crate) fn running_state(&self, cx: &mut App) -> Option<Entity<RunningState>> {        self.active_session()            .map(|session| session.read(cx).running_state().clone())    }     pub fn project(&self) -> &Entity<Project> {        &self.project    }     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);                        },                    )                });                 workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));                 debug_panel            })        })    }     pub fn start_session(        &mut self,        scenario: DebugScenario,        task_context: SharedTaskContext,        active_buffer: Option<Entity<Buffer>>,        worktree_id: Option<WorktreeId>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let dap_store = self.project.read(cx).dap_store();        let Some(adapter) = DapRegistry::global(cx).adapter(&scenario.adapter) else {            return;        };        let quirks = SessionQuirks {            compact: adapter.compact_child_session(),            prefer_thread_name: adapter.prefer_thread_name(),        };        let session = dap_store.update(cx, |dap_store, cx| {            dap_store.new_session(                Some(scenario.label.clone()),                DebugAdapterName(scenario.adapter.clone()),                task_context.clone(),                None,                quirks,                cx,            )        });        let worktree = worktree_id.or_else(|| {            active_buffer                .as_ref()                .and_then(|buffer| buffer.read(cx).file())                .map(|f| f.worktree_id(cx))        });         let Some(worktree) = worktree            .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))            .or_else(|| self.project.read(cx).visible_worktrees(cx).next())        else {            log::debug!("Could not find a worktree to spawn the debug session in");            return;        };         self.debug_scenario_scheduled_last = true;        if let Some(inventory) = self            .project            .read(cx)            .task_store()            .read(cx)            .task_inventory()            .cloned()        {            inventory.update(cx, |inventory, _| {                inventory.scenario_scheduled(                    scenario.clone(),                    task_context.clone(),                    worktree_id,                    active_buffer.as_ref().map(|buffer| buffer.downgrade()),                );            })        }        let task = cx.spawn_in(window, {            let session = session.clone();            async move |this, cx| {                let debug_session =                    Self::register_session(this.clone(), session.clone(), true, cx).await?;                let definition = debug_session                    .update_in(cx, |debug_session, window, cx| {                        debug_session.running_state().update(cx, |running, cx| {                            if scenario.build.is_some() {                                running.scenario = Some(scenario.clone());                                running.scenario_context = Some(DebugScenarioContext {                                    active_buffer: active_buffer                                        .as_ref()                                        .map(|entity| entity.downgrade()),                                    task_context: task_context.clone(),                                    worktree_id,                                });                            };                            running.resolve_scenario(                                scenario,                                task_context,                                active_buffer,                                worktree_id,                                window,                                cx,                            )                        })                    })?                    .await?;                dap_store                    .update(cx, |dap_store, cx| {                        dap_store.boot_session(session.clone(), definition, worktree, cx)                    })                    .await            }        });         let boot_task = cx.spawn({            let session = session.clone();             async move |_, cx| {                if let Err(error) = task.await {                    let redacted_error = redact_command(&format!("{error:#}"));                    log::error!("{redacted_error}");                    session                        .update(cx, |session, cx| {                            session                                .console_output(cx)                                .unbounded_send(format!("error: {:#}", redacted_error))                                .ok();                            session.shutdown(cx)                        })                        .await;                }                anyhow::Ok(())            }        });         session.update(cx, |session, _| match &mut session.state {            SessionState::Booting(state_task) => {                *state_task = Some(boot_task);            }            SessionState::Running(_) => {                debug_panic!("Session state should be in building because we are just starting it");            }        });    }     pub(crate) fn rerun_last_session(        &mut self,        workspace: &mut Workspace,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let task_store = workspace.project().read(cx).task_store().clone();        let Some(task_inventory) = task_store.read(cx).task_inventory() else {            return;        };        let workspace = self.workspace.clone();        let Some((scenario, context)) = task_inventory.read(cx).last_scheduled_scenario().cloned()        else {            window.defer(cx, move |window, cx| {                workspace                    .update(cx, |workspace, cx| {                        NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);                    })                    .ok();            });            return;        };         let DebugScenarioContext {            task_context,            worktree_id,            active_buffer,        } = context;         let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());         self.start_session(            scenario,            task_context,            active_buffer,            worktree_id,            window,            cx,        );    }     pub(crate) async fn register_session(        this: WeakEntity<Self>,        session: Entity<Session>,        focus: bool,        cx: &mut AsyncWindowContext,    ) -> Result<Entity<DebugSession>> {        let debug_session = register_session_inner(&this, session, cx).await?;         let workspace = this.update_in(cx, |this, window, cx| {            if focus {                this.activate_session(debug_session.clone(), window, cx);            }             this.workspace.clone()        })?;        workspace.update_in(cx, |workspace, window, cx| {            workspace.focus_panel::<Self>(window, cx);        })?;        Ok(debug_session)    }     pub(crate) fn handle_restart_request(        &mut self,        mut curr_session: Entity<Session>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        while let Some(parent_session) = curr_session.read(cx).parent_session().cloned() {            curr_session = parent_session;        }         let Some(worktree) = curr_session.read(cx).worktree() else {            log::error!("Attempted to restart a non-running session");            return;        };         let dap_store_handle = self.project.read(cx).dap_store();        let label = curr_session.read(cx).label();        let quirks = curr_session.read(cx).quirks();        let adapter = curr_session.read(cx).adapter();        let binary = curr_session.read(cx).binary().cloned().unwrap();        let task_context = curr_session.read(cx).task_context().clone();         let curr_session_id = curr_session.read(cx).session_id();        self.sessions_with_children            .retain(|session, _| session.read(cx).session_id(cx) != curr_session_id);        let task = dap_store_handle.update(cx, |dap_store, cx| {            dap_store.shutdown_session(curr_session_id, cx)        });         cx.spawn_in(window, async move |this, cx| {            task.await.log_err();             let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {                let session = dap_store.new_session(label, adapter, task_context, None, quirks, cx);                 let task = session.update(cx, |session, cx| {                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)                });                (session, task)            });            Self::register_session(this.clone(), session.clone(), true, cx).await?;             if let Err(error) = task.await {                session                    .update(cx, |session, cx| {                        session                            .console_output(cx)                            .unbounded_send(format!(                                "Session failed to restart with error: {}",                                error                            ))                            .ok();                        session.shutdown(cx)                    })                    .await;                 return Err(error);            };             Ok(())        })        .detach_and_log_err(cx);    }     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 a non-running session");            return;        };         let dap_store_handle = self.project.read(cx).dap_store();        let label = self.label_for_child_session(&parent_session, request, cx);        let adapter = parent_session.read(cx).adapter();        let quirks = parent_session.read(cx).quirks();        let Some(mut binary) = parent_session.read(cx).binary().cloned() else {            log::error!("Attempted to start a child-session without a binary");            return;        };        let task_context = parent_session.read(cx).task_context().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(                    label,                    adapter,                    task_context,                    Some(parent_session.clone()),                    quirks,                    cx,                );                 let task = session.update(cx, |session, cx| {                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)                });                (session, task)            });            // Focus child sessions if the parent has never emitted a stopped event;            // this improves our JavaScript experience, as it always spawns a "main" session that then spawns subsessions.            let parent_ever_stopped = parent_session.update(cx, |this, _| this.has_ever_stopped());            Self::register_session(this, session, !parent_ever_stopped, cx).await?;            task.await        })        .detach_and_log_err(cx);    }     pub(crate) fn close_session(        &mut self,        entity_id: EntityId,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let Some(session) = self            .sessions_with_children            .keys()            .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| {

Lines 1–500 of 2009.