933 lines · 38.3 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::{    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,    client::SessionId, debugger_settings::DebuggerSettings,};use futures::{SinkExt as _, channel::mpsc};use gpui::{    Action, App, AsyncWindowContext, Context, Entity, EntityId, EventEmitter, FocusHandle,    Focusable, Subscription, Task, WeakEntity, actions,}; 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;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: WeakEntity<Project>,    workspace: WeakEntity<Workspace>,    focus_handle: FocusHandle,    _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 project = workspace.project().clone();                                    workspace.toggle_modal(window, cx, |window, cx| {                                        crate::attach_modal::AttachModal::new(                                            project,                                            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: project.downgrade(),                workspace: workspace.weak_handle(),            };             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: 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();                 debug_panel            })        })    }     pub fn active_session(&self) -> Option<Entity<DebugSession>> {        self.active_session.clone()    }     pub fn debug_panel_items_by_client(        &self,        client_id: &SessionId,        cx: &Context<Self>,    ) -> Vec<Entity<DebugSession>> {        self.sessions            .iter()            .filter(|item| item.read(cx).session_id(cx) == *client_id)            .map(|item| item.clone())            .collect()    }     pub fn debug_panel_item_by_client(        &self,        client_id: SessionId,        cx: &mut Context<Self>,    ) -> Option<Entity<DebugSession>> {        self.sessions            .iter()            .find(|item| {                let item = item.read(cx);                 item.session_id(cx) == client_id            })            .cloned()    }     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::DebugSessionInitialized(session_id) => {                let Some(session) = dap_store.read(cx).session_by_id(session_id) else {                    return log::error!(                        "Couldn't get session with id: {session_id:?} from DebugClientStarted event"                    );                };                 let adapter_name = session.read(cx).adapter_name();                 let session_id = *session_id;                cx.spawn_in(window, async move |this, cx| {                    let serialized_layout =                        persistence::get_serialized_pane_layout(adapter_name).await;                     this.update_in(cx, |this, window, cx| {                        let Some(project) = this.project.upgrade() else {                            return log::error!(                                "Debug Panel out lived it's weak reference to Project"                            );                        };                         if this                            .sessions                            .iter()                            .any(|item| item.read(cx).session_id(cx) == session_id)                        {                            // We already have an item for this session.                            return;                        }                        let session_item = DebugSession::running(                            project,                            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);                    })                })                .detach();            }            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);            }            _ => {}        }    }     fn handle_run_in_terminal_request(        &self,        title: Option<String>,        cwd: Option<Arc<Path>>,        command: Option<String>,        args: Vec<String>,        envs: HashMap<String, String>,        mut sender: mpsc::Sender<Result<u32>>,        window: &mut Window,        cx: &mut App,    ) -> Task<Result<()>> {        let terminal_task = self.workspace.update(cx, |workspace, cx| {            let terminal_panel = workspace.panel::<TerminalPanel>(cx).ok_or_else(|| {                anyhow!("RunInTerminal DAP request failed because TerminalPanel wasn't found")            });             let terminal_panel = match terminal_panel {                Ok(panel) => panel,                Err(err) => return Task::ready(Err(err)),            };             terminal_panel.update(cx, |terminal_panel, cx| {                let terminal_task = terminal_panel.add_terminal(                    TerminalKind::Debug {                        command,                        args,                        envs,                        cwd,                        title,                    },                    task::RevealStrategy::Always,                    window,                    cx,                );                 cx.spawn(async move |_, cx| {                    let pid_task = async move {                        let terminal = terminal_task.await?;                         terminal.read_with(cx, |terminal, _| terminal.pty_info.pid())                    };                     pid_task.await                })            })        });         cx.background_spawn(async move {            match terminal_task {                Ok(pid_task) => match pid_task.await {                    Ok(Some(pid)) => sender.send(Ok(pid.as_u32())).await?,                    Ok(None) => {                        sender                            .send(Err(anyhow!(                                "Terminal was spawned but PID was not available"                            )))                            .await?                    }                    Err(error) => sender.send(Err(anyhow!(error))).await?,                },                Err(error) => sender.send(Err(anyhow!(error))).await?,            };             Ok(())        })    }     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;        };         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())            })            .ok()            .flatten()            .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,                    &["Yes", "No"],                );                if response.await == Ok(1) {                    return;                }            }            session.update(cx, |session, cx| session.shutdown(cx)).ok();            this.update(cx, |this, cx| {                this.sessions.retain(|other| entity_id != other.entity_id());                 if let Some(active_session_id) = this                    .active_session                    .as_ref()                    .map(|session| session.entity_id())                {                    if active_session_id == entity_id {                        this.active_session = this.sessions.first().cloned();                    }                }                cx.notify()            })            .ok();        })        .detach();    }    fn sessions_drop_down_menu(        &self,        active_session: &Entity<DebugSession>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> DropdownMenu {        let sessions = self.sessions.clone();        let weak = cx.weak_entity();        let label = active_session.read(cx).label_element(cx);         DropdownMenu::new_with_element(            "debugger-session-list",            label,            ContextMenu::build(window, cx, move |mut this, _, cx| {                let context_menu = cx.weak_entity();                for session in sessions.into_iter() {                    let weak_session = session.downgrade();

Lines 1–500 of 933.