Julia Ryanwipbded0e11 year agoHistory
1836 lines · 74.0 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 dap::adapters::DebugAdapterName;use dap::debugger_settings::DebugPanelDockPosition;use dap::{    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,    client::SessionId, debugger_settings::DebuggerSettings,};use dap::{DapRegistry, StartDebuggingRequestArguments};use editor::Editor;use gpui::{    Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId,    EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task,    WeakEntity, anchored, deferred,};use text::ToPoint as _; use itertools::Itertools as _;use language::Buffer;use project::debugger::session::{Session, 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, TaskContext};use tree_sitter::{Query, StreamingIterator as _};use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};use util::{ResultExt, maybe};use workspace::item::SaveOptions;use workspace::{    Item, Pane, Workspace,    dock::{DockPosition, Panel, PanelEvent},};use workspace::{OpenInDebugJson, SplitDirection};use zed_actions::ToggleFocus; 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),} pub struct DebugPanel {    size: Pixels,    sessions: Vec<Entity<DebugSession>>,    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) 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);                },            );             if let Some(entity) = workspace.weak_handle().upgrade() {                let edit_scenario_subscription = cx.subscribe_in(                    &entity,                    window,                    move |this, workspace, OpenInDebugJson { scenario, id }, window, cx| {                        let task = this.go_to_scenario_definition(                            TaskSourceKind::UserInput,                            scenario.clone(),                            todo!(),                            // *id,                            window,                            cx,                        );                        cx.spawn(async move |_, cx| task.await)                            .detach_and_log_err(cx);                    },                );            }             Self {                size: px(300.),                sessions: vec![],                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);        });    }     pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {        self.sessions.clone()    }     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 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: TaskContext,        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 session = dap_store.update(cx, |dap_store, cx| {            dap_store.new_session(                scenario.label.clone(),                DebugAdapterName(scenario.adapter.clone()),                task_context.clone(),                None,                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(),                    // todo(debugger): Task context is cloned three times                    // once in Session,inventory, and in resolve scenario                    // we should wrap it in an RC instead to save some memory                    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: 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            }        });         cx.spawn(async move |_, cx| {            if let Err(error) = task.await {                log::error!("{error}");                session                    .update(cx, |session, cx| {                        session                            .console_output(cx)                            .unbounded_send(format!("error: {}", error))                            .ok();                        session.shutdown(cx)                    })?                    .await;            }            anyhow::Ok(())        })        .detach_and_log_err(cx);    }     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().clone();        let label = curr_session.read(cx).label().clone();        let adapter = curr_session.read(cx).adapter().clone();        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            .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, 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().clone();        let label = self.label_for_child_session(&parent_session, request, cx);        let adapter = parent_session.read(cx).adapter().clone();        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()),                    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            .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| {

Lines 1–500 of 1836.