791 lines · 32.3 KB · Rust
RawDownload
use crate::{    ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack,    StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,};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, path::PathBuf};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 _subscriptions =                vec![cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event)];             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        })    }     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(&debug_panel, |_, debug_panel, cx| {                    let (has_active_session, supports_restart, support_step_back) = debug_panel                        .update(cx, |this, cx| {                            this.active_session()                                .map(|item| {                                    let running = item.read(cx).mode().as_running().cloned();                                     match running {                                        Some(running) => {                                            let caps = running.read(cx).capabilities(cx);                                            (                                                true,                                                caps.supports_restart_request.unwrap_or_default(),                                                caps.supports_step_back.unwrap_or_default(),                                            )                                        }                                        None => (false, false, false),                                    }                                })                                .unwrap_or((false, false, false))                        });                     let filter = CommandPaletteFilter::global_mut(cx);                    let debugger_action_types = [                        TypeId::of::<Continue>(),                        TypeId::of::<StepOver>(),                        TypeId::of::<StepInto>(),                        TypeId::of::<StepOut>(),                        TypeId::of::<Stop>(),                        TypeId::of::<Disconnect>(),                        TypeId::of::<Pause>(),                        TypeId::of::<ToggleIgnoreBreakpoints>(),                    ];                     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);                        }                    } 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);                    }                })                .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 Some(project) = self.project.upgrade() else {                    return log::error!("Debug Panel out lived it's weak reference to Project");                };                 if self                    .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,                    self.workspace.clone(),                    session,                    cx.weak_entity(),                    window,                    cx,                );                 self.sessions.push(session_item.clone());                self.activate_session(session_item, window, cx);            }            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: PathBuf,        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, cx: &mut Context<Self>) {        let Some(session) = self            .sessions            .iter()            .find(|other| entity_id == other.entity_id())        else {            return;        };         session.update(cx, |session, cx| session.shutdown(cx));         self.sessions.retain(|other| entity_id != other.entity_id());         if let Some(active_session_id) = self            .active_session            .as_ref()            .map(|session| session.entity_id())        {            if active_session_id == entity_id {                self.active_session = self.sessions.first().cloned();            }        }    }     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, _, _| {                for session in sessions.into_iter() {                    let weak_session = session.downgrade();                    let weak_id = weak_session.entity_id();                     this = this.custom_entry(                        {                            let weak = weak.clone();                            move |_, cx| {                                weak_session                                    .read_with(cx, |session, cx| {                                        h_flex()                                            .w_full()                                            .justify_between()                                            .child(session.label_element(cx))                                            .child(                                                IconButton::new(                                                    "close-debug-session",                                                    IconName::Close,                                                )                                                .icon_size(IconSize::Small)                                                .on_click({                                                    let weak = weak.clone();                                                    move |_, _, cx| {                                                        weak.update(cx, |panel, cx| {                                                            panel.close_session(weak_id, cx);                                                        })                                                        .ok();                                                    }                                                }),                                            )                                            .into_any_element()                                    })                                    .unwrap_or_else(|_| div().into_any_element())                            }                        },                        {                            let weak = weak.clone();                            move |window, cx| {                                weak.update(cx, |panel, cx| {                                    panel.activate_session(session.clone(), window, cx);                                })                                .ok();                            }                        },                    );                }                this            }),        )    }     fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {        let active_session = self.active_session.clone();         Some(            h_flex()                .border_b_1()                .border_color(cx.theme().colors().border)                .p_1()                .justify_between()                .w_full()                .child(                    h_flex().gap_2().w_full().when_some(                        active_session                            .as_ref()                            .and_then(|session| session.read(cx).mode().as_running()),                        |this, running_session| {                            let thread_status = running_session                                .read(cx)                                .thread_status(cx)                                .unwrap_or(project::debugger::session::ThreadStatus::Exited);                            let capabilities = running_session.read(cx).capabilities(cx);                            this.map(|this| {                                if thread_status == ThreadStatus::Running {                                    this.child(                                        IconButton::new("debug-pause", IconName::DebugPause)                                            .icon_size(IconSize::XSmall)                                            .shape(ui::IconButtonShape::Square)                                            .on_click(window.listener_for(                                                &running_session,                                                |this, _, _window, cx| {                                                    this.pause_thread(cx);                                                },                                            ))                                            .tooltip(move |window, cx| {                                                Tooltip::text("Pause program")(window, cx)                                            }),                                    )                                } else {                                    this.child(                                        IconButton::new("debug-continue", IconName::DebugContinue)                                            .icon_size(IconSize::XSmall)                                            .shape(ui::IconButtonShape::Square)                                            .on_click(window.listener_for(                                                &running_session,                                                |this, _, _window, cx| this.continue_thread(cx),                                            ))                                            .disabled(thread_status != ThreadStatus::Stopped)                                            .tooltip(move |window, cx| {                                                Tooltip::text("Continue program")(window, cx)                                            }),                                    )                                }                            })                            .child(                                IconButton::new("debug-step-over", IconName::ArrowRight)                                    .icon_size(IconSize::XSmall)                                    .shape(ui::IconButtonShape::Square)                                    .on_click(window.listener_for(                                        &running_session,                                        |this, _, _window, cx| {                                            this.step_over(cx);                                        },                                    ))                                    .disabled(thread_status != ThreadStatus::Stopped)                                    .tooltip(move |window, cx| {                                        Tooltip::text("Step over")(window, cx)                                    }),                            )                            .child(                                IconButton::new("debug-step-out", IconName::ArrowUpRight)                                    .icon_size(IconSize::XSmall)                                    .shape(ui::IconButtonShape::Square)                                    .on_click(window.listener_for(

Lines 1–500 of 791.