debugger_panel.rs

   1use crate::persistence::DebuggerPaneItem;
   2use crate::session::DebugSession;
   3use crate::session::running::RunningState;
   4use crate::{
   5    ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
   6    FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
   7    ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
   8    ToggleSessionPicker, ToggleThreadPicker, persistence,
   9};
  10use anyhow::{Context as _, Result, anyhow};
  11use command_palette_hooks::CommandPaletteFilter;
  12use dap::StartDebuggingRequestArguments;
  13use dap::adapters::DebugAdapterName;
  14use dap::debugger_settings::DebugPanelDockPosition;
  15use dap::{
  16    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
  17    client::SessionId, debugger_settings::DebuggerSettings,
  18};
  19use gpui::{
  20    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
  21    FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
  22    actions, anchored, deferred,
  23};
  24
  25use language::Buffer;
  26use project::debugger::session::{Session, SessionStateEvent};
  27use project::{Fs, ProjectPath, WorktreeId};
  28use project::{Project, debugger::session::ThreadStatus};
  29use rpc::proto::{self};
  30use settings::Settings;
  31use std::any::TypeId;
  32use std::sync::Arc;
  33use task::{DebugScenario, TaskContext};
  34use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
  35use workspace::SplitDirection;
  36use workspace::{
  37    Pane, Workspace,
  38    dock::{DockPosition, Panel, PanelEvent},
  39};
  40
  41pub enum DebugPanelEvent {
  42    Exited(SessionId),
  43    Terminated(SessionId),
  44    Stopped {
  45        client_id: SessionId,
  46        event: StoppedEvent,
  47        go_to_stack_frame: bool,
  48    },
  49    Thread((SessionId, ThreadEvent)),
  50    Continued((SessionId, ContinuedEvent)),
  51    Output((SessionId, OutputEvent)),
  52    Module((SessionId, ModuleEvent)),
  53    LoadedSource((SessionId, LoadedSourceEvent)),
  54    ClientShutdown(SessionId),
  55    CapabilitiesChanged(SessionId),
  56}
  57
  58actions!(debug_panel, [ToggleFocus]);
  59
  60pub struct DebugPanel {
  61    size: Pixels,
  62    sessions: Vec<Entity<DebugSession>>,
  63    active_session: Option<Entity<DebugSession>>,
  64    project: Entity<Project>,
  65    workspace: WeakEntity<Workspace>,
  66    focus_handle: FocusHandle,
  67    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
  68    pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
  69    pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
  70    fs: Arc<dyn Fs>,
  71    _subscriptions: [Subscription; 1],
  72}
  73
  74impl DebugPanel {
  75    pub fn new(
  76        workspace: &Workspace,
  77        window: &mut Window,
  78        cx: &mut Context<Workspace>,
  79    ) -> Entity<Self> {
  80        cx.new(|cx| {
  81            let project = workspace.project().clone();
  82            let focus_handle = cx.focus_handle();
  83            let thread_picker_menu_handle = PopoverMenuHandle::default();
  84            let session_picker_menu_handle = PopoverMenuHandle::default();
  85
  86            let focus_subscription = cx.on_focus(
  87                &focus_handle,
  88                window,
  89                |this: &mut DebugPanel, window, cx| {
  90                    this.focus_active_item(window, cx);
  91                },
  92            );
  93
  94            Self {
  95                size: px(300.),
  96                sessions: vec![],
  97                active_session: None,
  98                focus_handle,
  99                project,
 100                workspace: workspace.weak_handle(),
 101                context_menu: None,
 102                fs: workspace.app_state().fs.clone(),
 103                thread_picker_menu_handle,
 104                session_picker_menu_handle,
 105                _subscriptions: [focus_subscription],
 106            }
 107        })
 108    }
 109
 110    pub(crate) fn focus_active_item(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 111        let Some(session) = self.active_session.clone() else {
 112            return;
 113        };
 114        let active_pane = session
 115            .read(cx)
 116            .running_state()
 117            .read(cx)
 118            .active_pane()
 119            .clone();
 120        active_pane.update(cx, |pane, cx| {
 121            pane.focus_active_item(window, cx);
 122        });
 123    }
 124
 125    pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
 126        self.sessions.clone()
 127    }
 128
 129    pub fn active_session(&self) -> Option<Entity<DebugSession>> {
 130        self.active_session.clone()
 131    }
 132
 133    pub(crate) fn running_state(&self, cx: &mut App) -> Option<Entity<RunningState>> {
 134        self.active_session()
 135            .map(|session| session.read(cx).running_state().clone())
 136    }
 137
 138    pub(crate) fn filter_action_types(&self, cx: &mut App) {
 139        let (has_active_session, supports_restart, support_step_back, status) = self
 140            .active_session()
 141            .map(|item| {
 142                let running = item.read(cx).running_state().clone();
 143                let caps = running.read(cx).capabilities(cx);
 144                (
 145                    !running.read(cx).session().read(cx).is_terminated(),
 146                    caps.supports_restart_request.unwrap_or_default(),
 147                    caps.supports_step_back.unwrap_or_default(),
 148                    running.read(cx).thread_status(cx),
 149                )
 150            })
 151            .unwrap_or((false, false, false, None));
 152
 153        let filter = CommandPaletteFilter::global_mut(cx);
 154        let debugger_action_types = [
 155            TypeId::of::<Detach>(),
 156            TypeId::of::<Stop>(),
 157            TypeId::of::<ToggleIgnoreBreakpoints>(),
 158        ];
 159
 160        let running_action_types = [TypeId::of::<Pause>()];
 161
 162        let stopped_action_type = [
 163            TypeId::of::<Continue>(),
 164            TypeId::of::<StepOver>(),
 165            TypeId::of::<StepInto>(),
 166            TypeId::of::<StepOut>(),
 167            TypeId::of::<ShowStackTrace>(),
 168            TypeId::of::<editor::actions::DebuggerRunToCursor>(),
 169            TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
 170        ];
 171
 172        let step_back_action_type = [TypeId::of::<StepBack>()];
 173        let restart_action_type = [TypeId::of::<Restart>()];
 174
 175        if has_active_session {
 176            filter.show_action_types(debugger_action_types.iter());
 177
 178            if supports_restart {
 179                filter.show_action_types(restart_action_type.iter());
 180            } else {
 181                filter.hide_action_types(&restart_action_type);
 182            }
 183
 184            if support_step_back {
 185                filter.show_action_types(step_back_action_type.iter());
 186            } else {
 187                filter.hide_action_types(&step_back_action_type);
 188            }
 189
 190            match status {
 191                Some(ThreadStatus::Running) => {
 192                    filter.show_action_types(running_action_types.iter());
 193                    filter.hide_action_types(&stopped_action_type);
 194                }
 195                Some(ThreadStatus::Stopped) => {
 196                    filter.show_action_types(stopped_action_type.iter());
 197                    filter.hide_action_types(&running_action_types);
 198                }
 199                _ => {
 200                    filter.hide_action_types(&running_action_types);
 201                    filter.hide_action_types(&stopped_action_type);
 202                }
 203            }
 204        } else {
 205            // show only the `debug: start`
 206            filter.hide_action_types(&debugger_action_types);
 207            filter.hide_action_types(&step_back_action_type);
 208            filter.hide_action_types(&restart_action_type);
 209            filter.hide_action_types(&running_action_types);
 210            filter.hide_action_types(&stopped_action_type);
 211        }
 212    }
 213
 214    pub fn load(
 215        workspace: WeakEntity<Workspace>,
 216        cx: &mut AsyncWindowContext,
 217    ) -> Task<Result<Entity<Self>>> {
 218        cx.spawn(async move |cx| {
 219            workspace.update_in(cx, |workspace, window, cx| {
 220                let debug_panel = DebugPanel::new(workspace, window, cx);
 221
 222                workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
 223                    workspace.project().read(cx).breakpoint_store().update(
 224                        cx,
 225                        |breakpoint_store, cx| {
 226                            breakpoint_store.clear_breakpoints(cx);
 227                        },
 228                    )
 229                });
 230
 231                cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
 232                    Self::filter_action_types(debug_panel, cx);
 233                })
 234                .detach();
 235
 236                cx.observe(&debug_panel, |_, debug_panel, cx| {
 237                    debug_panel.update(cx, |debug_panel, cx| {
 238                        Self::filter_action_types(debug_panel, cx);
 239                    });
 240                })
 241                .detach();
 242                workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
 243
 244                debug_panel
 245            })
 246        })
 247    }
 248
 249    pub fn start_session(
 250        &mut self,
 251        scenario: DebugScenario,
 252        task_context: TaskContext,
 253        active_buffer: Option<Entity<Buffer>>,
 254        worktree_id: Option<WorktreeId>,
 255        window: &mut Window,
 256        cx: &mut Context<Self>,
 257    ) {
 258        let dap_store = self.project.read(cx).dap_store();
 259        let session = dap_store.update(cx, |dap_store, cx| {
 260            dap_store.new_session(
 261                scenario.label.clone(),
 262                DebugAdapterName(scenario.adapter.clone()),
 263                None,
 264                cx,
 265            )
 266        });
 267        if let Some(inventory) = self
 268            .project
 269            .read(cx)
 270            .task_store()
 271            .read(cx)
 272            .task_inventory()
 273            .cloned()
 274        {
 275            inventory.update(cx, |inventory, _| {
 276                inventory.scenario_scheduled(scenario.clone());
 277            })
 278        }
 279        let task = cx.spawn_in(window, {
 280            let session = session.clone();
 281            async move |this, cx| {
 282                let debug_session =
 283                    Self::register_session(this.clone(), session.clone(), true, cx).await?;
 284                let definition = debug_session
 285                    .update_in(cx, |debug_session, window, cx| {
 286                        debug_session.running_state().update(cx, |running, cx| {
 287                            running.resolve_scenario(
 288                                scenario,
 289                                task_context,
 290                                active_buffer,
 291                                worktree_id,
 292                                window,
 293                                cx,
 294                            )
 295                        })
 296                    })?
 297                    .await?;
 298                dap_store
 299                    .update(cx, |dap_store, cx| {
 300                        dap_store.boot_session(session.clone(), definition, cx)
 301                    })?
 302                    .await
 303            }
 304        });
 305
 306        cx.spawn(async move |_, cx| {
 307            if let Err(error) = task.await {
 308                log::error!("{error}");
 309                session
 310                    .update(cx, |session, cx| {
 311                        session
 312                            .console_output(cx)
 313                            .unbounded_send(format!("error: {}", error))
 314                            .ok();
 315                        session.shutdown(cx)
 316                    })?
 317                    .await;
 318            }
 319            anyhow::Ok(())
 320        })
 321        .detach_and_log_err(cx);
 322    }
 323
 324    pub(crate) fn rerun_last_session(
 325        &mut self,
 326        workspace: &mut Workspace,
 327        window: &mut Window,
 328        cx: &mut Context<Self>,
 329    ) {
 330        let task_store = workspace.project().read(cx).task_store().clone();
 331        let Some(task_inventory) = task_store.read(cx).task_inventory() else {
 332            return;
 333        };
 334        let Some(scenario) = task_inventory.read(cx).last_scheduled_scenario().cloned() else {
 335            return;
 336        };
 337        let workspace = self.workspace.clone();
 338
 339        cx.spawn_in(window, async move |this, cx| {
 340            let task_contexts = workspace
 341                .update_in(cx, |workspace, window, cx| {
 342                    tasks_ui::task_contexts(workspace, window, cx)
 343                })?
 344                .await;
 345
 346            let task_context = task_contexts.active_context().cloned().unwrap_or_default();
 347            let worktree_id = task_contexts.worktree();
 348
 349            this.update_in(cx, |this, window, cx| {
 350                this.start_session(
 351                    scenario.clone(),
 352                    task_context,
 353                    None,
 354                    worktree_id,
 355                    window,
 356                    cx,
 357                );
 358            })
 359        })
 360        .detach();
 361    }
 362
 363    pub(crate) async fn register_session(
 364        this: WeakEntity<Self>,
 365        session: Entity<Session>,
 366        focus: bool,
 367        cx: &mut AsyncWindowContext,
 368    ) -> Result<Entity<DebugSession>> {
 369        let debug_session = register_session_inner(&this, session, cx).await?;
 370
 371        let workspace = this.update_in(cx, |this, window, cx| {
 372            if focus {
 373                this.activate_session(debug_session.clone(), window, cx);
 374            }
 375
 376            this.workspace.clone()
 377        })?;
 378        workspace.update_in(cx, |workspace, window, cx| {
 379            workspace.focus_panel::<Self>(window, cx);
 380        })?;
 381        Ok(debug_session)
 382    }
 383
 384    pub(crate) fn handle_restart_request(
 385        &mut self,
 386        mut curr_session: Entity<Session>,
 387        window: &mut Window,
 388        cx: &mut Context<Self>,
 389    ) {
 390        while let Some(parent_session) = curr_session.read(cx).parent_session().cloned() {
 391            curr_session = parent_session;
 392        }
 393
 394        let Some(worktree) = curr_session.read(cx).worktree() else {
 395            log::error!("Attempted to restart a non-running session");
 396            return;
 397        };
 398
 399        let dap_store_handle = self.project.read(cx).dap_store().clone();
 400        let label = curr_session.read(cx).label().clone();
 401        let adapter = curr_session.read(cx).adapter().clone();
 402        let binary = curr_session.read(cx).binary().clone();
 403        let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
 404
 405        cx.spawn_in(window, async move |this, cx| {
 406            task.await;
 407
 408            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 409                let session = dap_store.new_session(label, adapter, None, cx);
 410
 411                let task = session.update(cx, |session, cx| {
 412                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 413                });
 414                (session, task)
 415            })?;
 416            Self::register_session(this.clone(), session, true, cx).await?;
 417            task.await
 418        })
 419        .detach_and_log_err(cx);
 420    }
 421
 422    pub fn handle_start_debugging_request(
 423        &mut self,
 424        request: &StartDebuggingRequestArguments,
 425        parent_session: Entity<Session>,
 426        window: &mut Window,
 427        cx: &mut Context<Self>,
 428    ) {
 429        let Some(worktree) = parent_session.read(cx).worktree() else {
 430            log::error!("Attempted to start a child-session from a non-running session");
 431            return;
 432        };
 433
 434        let dap_store_handle = self.project.read(cx).dap_store().clone();
 435        let mut label = parent_session.read(cx).label().clone();
 436        if !label.ends_with("(child)") {
 437            label = format!("{label} (child)").into();
 438        }
 439        let adapter = parent_session.read(cx).adapter().clone();
 440        let mut binary = parent_session.read(cx).binary().clone();
 441        binary.request_args = request.clone();
 442        cx.spawn_in(window, async move |this, cx| {
 443            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 444                let session =
 445                    dap_store.new_session(label, adapter, Some(parent_session.clone()), cx);
 446
 447                let task = session.update(cx, |session, cx| {
 448                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 449                });
 450                (session, task)
 451            })?;
 452            Self::register_session(this, session, false, cx).await?;
 453            task.await
 454        })
 455        .detach_and_log_err(cx);
 456    }
 457
 458    pub(crate) fn close_session(
 459        &mut self,
 460        entity_id: EntityId,
 461        window: &mut Window,
 462        cx: &mut Context<Self>,
 463    ) {
 464        let Some(session) = self
 465            .sessions
 466            .iter()
 467            .find(|other| entity_id == other.entity_id())
 468            .cloned()
 469        else {
 470            return;
 471        };
 472        session.update(cx, |this, cx| {
 473            this.running_state().update(cx, |this, cx| {
 474                this.serialize_layout(window, cx);
 475            });
 476        });
 477        let session_id = session.update(cx, |this, cx| this.session_id(cx));
 478        let should_prompt = self
 479            .project
 480            .update(cx, |this, cx| {
 481                let session = this.dap_store().read(cx).session_by_id(session_id);
 482                session.map(|session| !session.read(cx).is_terminated())
 483            })
 484            .unwrap_or_default();
 485
 486        cx.spawn_in(window, async move |this, cx| {
 487            if should_prompt {
 488                let response = cx.prompt(
 489                    gpui::PromptLevel::Warning,
 490                    "This Debug Session is still running. Are you sure you want to terminate it?",
 491                    None,
 492                    &["Yes", "No"],
 493                );
 494                if response.await == Ok(1) {
 495                    return;
 496                }
 497            }
 498            session.update(cx, |session, cx| session.shutdown(cx)).ok();
 499            this.update(cx, |this, cx| {
 500                this.sessions.retain(|other| entity_id != other.entity_id());
 501
 502                if let Some(active_session_id) = this
 503                    .active_session
 504                    .as_ref()
 505                    .map(|session| session.entity_id())
 506                {
 507                    if active_session_id == entity_id {
 508                        this.active_session = this.sessions.first().cloned();
 509                    }
 510                }
 511                cx.notify()
 512            })
 513            .ok();
 514        })
 515        .detach();
 516    }
 517
 518    pub(crate) fn deploy_context_menu(
 519        &mut self,
 520        position: Point<Pixels>,
 521        window: &mut Window,
 522        cx: &mut Context<Self>,
 523    ) {
 524        if let Some(running_state) = self
 525            .active_session
 526            .as_ref()
 527            .map(|session| session.read(cx).running_state().clone())
 528        {
 529            let pane_items_status = running_state.read(cx).pane_items_status(cx);
 530            let this = cx.weak_entity();
 531
 532            let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
 533                for (item_kind, is_visible) in pane_items_status.into_iter() {
 534                    menu = menu.toggleable_entry(item_kind, is_visible, IconPosition::End, None, {
 535                        let this = this.clone();
 536                        move |window, cx| {
 537                            this.update(cx, |this, cx| {
 538                                if let Some(running_state) = this
 539                                    .active_session
 540                                    .as_ref()
 541                                    .map(|session| session.read(cx).running_state().clone())
 542                                {
 543                                    running_state.update(cx, |state, cx| {
 544                                        if is_visible {
 545                                            state.remove_pane_item(item_kind, window, cx);
 546                                        } else {
 547                                            state.add_pane_item(item_kind, position, window, cx);
 548                                        }
 549                                    })
 550                                }
 551                            })
 552                            .ok();
 553                        }
 554                    });
 555                }
 556
 557                menu
 558            });
 559
 560            window.focus(&context_menu.focus_handle(cx));
 561            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
 562                this.context_menu.take();
 563                cx.notify();
 564            });
 565            self.context_menu = Some((context_menu, position, subscription));
 566        }
 567    }
 568
 569    pub(crate) fn top_controls_strip(
 570        &mut self,
 571        window: &mut Window,
 572        cx: &mut Context<Self>,
 573    ) -> Option<Div> {
 574        let active_session = self.active_session.clone();
 575        let focus_handle = self.focus_handle.clone();
 576        let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
 577        let div = if is_side { v_flex() } else { h_flex() };
 578
 579        let new_session_button = || {
 580            IconButton::new("debug-new-session", IconName::Plus)
 581                .icon_size(IconSize::Small)
 582                .on_click({
 583                    move |_, window, cx| window.dispatch_action(crate::Start.boxed_clone(), cx)
 584                })
 585                .tooltip({
 586                    let focus_handle = focus_handle.clone();
 587                    move |window, cx| {
 588                        Tooltip::for_action_in(
 589                            "Start Debug Session",
 590                            &crate::Start,
 591                            &focus_handle,
 592                            window,
 593                            cx,
 594                        )
 595                    }
 596                })
 597        };
 598
 599        Some(
 600            div.border_b_1()
 601                .border_color(cx.theme().colors().border)
 602                .p_1()
 603                .justify_between()
 604                .w_full()
 605                .when(is_side, |this| this.gap_1())
 606                .child(
 607                    h_flex()
 608                        .child(
 609                            h_flex().gap_2().w_full().when_some(
 610                                active_session
 611                                    .as_ref()
 612                                    .map(|session| session.read(cx).running_state()),
 613                                |this, running_state| {
 614                                    let thread_status =
 615                                        running_state.read(cx).thread_status(cx).unwrap_or(
 616                                            project::debugger::session::ThreadStatus::Exited,
 617                                        );
 618                                    let capabilities = running_state.read(cx).capabilities(cx);
 619                                    this.map(|this| {
 620                                        if thread_status == ThreadStatus::Running {
 621                                            this.child(
 622                                                IconButton::new(
 623                                                    "debug-pause",
 624                                                    IconName::DebugPause,
 625                                                )
 626                                                .icon_size(IconSize::XSmall)
 627                                                .shape(ui::IconButtonShape::Square)
 628                                                .on_click(window.listener_for(
 629                                                    &running_state,
 630                                                    |this, _, _window, cx| {
 631                                                        this.pause_thread(cx);
 632                                                    },
 633                                                ))
 634                                                .tooltip({
 635                                                    let focus_handle = focus_handle.clone();
 636                                                    move |window, cx| {
 637                                                        Tooltip::for_action_in(
 638                                                            "Pause program",
 639                                                            &Pause,
 640                                                            &focus_handle,
 641                                                            window,
 642                                                            cx,
 643                                                        )
 644                                                    }
 645                                                }),
 646                                            )
 647                                        } else {
 648                                            this.child(
 649                                                IconButton::new(
 650                                                    "debug-continue",
 651                                                    IconName::DebugContinue,
 652                                                )
 653                                                .icon_size(IconSize::XSmall)
 654                                                .shape(ui::IconButtonShape::Square)
 655                                                .on_click(window.listener_for(
 656                                                    &running_state,
 657                                                    |this, _, _window, cx| this.continue_thread(cx),
 658                                                ))
 659                                                .disabled(thread_status != ThreadStatus::Stopped)
 660                                                .tooltip({
 661                                                    let focus_handle = focus_handle.clone();
 662                                                    move |window, cx| {
 663                                                        Tooltip::for_action_in(
 664                                                            "Continue program",
 665                                                            &Continue,
 666                                                            &focus_handle,
 667                                                            window,
 668                                                            cx,
 669                                                        )
 670                                                    }
 671                                                }),
 672                                            )
 673                                        }
 674                                    })
 675                                    .child(
 676                                        IconButton::new("debug-step-over", IconName::ArrowRight)
 677                                            .icon_size(IconSize::XSmall)
 678                                            .shape(ui::IconButtonShape::Square)
 679                                            .on_click(window.listener_for(
 680                                                &running_state,
 681                                                |this, _, _window, cx| {
 682                                                    this.step_over(cx);
 683                                                },
 684                                            ))
 685                                            .disabled(thread_status != ThreadStatus::Stopped)
 686                                            .tooltip({
 687                                                let focus_handle = focus_handle.clone();
 688                                                move |window, cx| {
 689                                                    Tooltip::for_action_in(
 690                                                        "Step over",
 691                                                        &StepOver,
 692                                                        &focus_handle,
 693                                                        window,
 694                                                        cx,
 695                                                    )
 696                                                }
 697                                            }),
 698                                    )
 699                                    .child(
 700                                        IconButton::new("debug-step-out", IconName::ArrowUpRight)
 701                                            .icon_size(IconSize::XSmall)
 702                                            .shape(ui::IconButtonShape::Square)
 703                                            .on_click(window.listener_for(
 704                                                &running_state,
 705                                                |this, _, _window, cx| {
 706                                                    this.step_out(cx);
 707                                                },
 708                                            ))
 709                                            .disabled(thread_status != ThreadStatus::Stopped)
 710                                            .tooltip({
 711                                                let focus_handle = focus_handle.clone();
 712                                                move |window, cx| {
 713                                                    Tooltip::for_action_in(
 714                                                        "Step out",
 715                                                        &StepOut,
 716                                                        &focus_handle,
 717                                                        window,
 718                                                        cx,
 719                                                    )
 720                                                }
 721                                            }),
 722                                    )
 723                                    .child(
 724                                        IconButton::new(
 725                                            "debug-step-into",
 726                                            IconName::ArrowDownRight,
 727                                        )
 728                                        .icon_size(IconSize::XSmall)
 729                                        .shape(ui::IconButtonShape::Square)
 730                                        .on_click(window.listener_for(
 731                                            &running_state,
 732                                            |this, _, _window, cx| {
 733                                                this.step_in(cx);
 734                                            },
 735                                        ))
 736                                        .disabled(thread_status != ThreadStatus::Stopped)
 737                                        .tooltip({
 738                                            let focus_handle = focus_handle.clone();
 739                                            move |window, cx| {
 740                                                Tooltip::for_action_in(
 741                                                    "Step in",
 742                                                    &StepInto,
 743                                                    &focus_handle,
 744                                                    window,
 745                                                    cx,
 746                                                )
 747                                            }
 748                                        }),
 749                                    )
 750                                    .child(Divider::vertical())
 751                                    .child(
 752                                        IconButton::new("debug-restart", IconName::DebugRestart)
 753                                            .icon_size(IconSize::XSmall)
 754                                            .on_click(window.listener_for(
 755                                                &running_state,
 756                                                |this, _, _window, cx| {
 757                                                    this.restart_session(cx);
 758                                                },
 759                                            ))
 760                                            .tooltip({
 761                                                let focus_handle = focus_handle.clone();
 762                                                move |window, cx| {
 763                                                    Tooltip::for_action_in(
 764                                                        "Restart",
 765                                                        &Restart,
 766                                                        &focus_handle,
 767                                                        window,
 768                                                        cx,
 769                                                    )
 770                                                }
 771                                            }),
 772                                    )
 773                                    .child(
 774                                        IconButton::new("debug-stop", IconName::Power)
 775                                            .icon_size(IconSize::XSmall)
 776                                            .on_click(window.listener_for(
 777                                                &running_state,
 778                                                |this, _, _window, cx| {
 779                                                    this.stop_thread(cx);
 780                                                },
 781                                            ))
 782                                            .disabled(
 783                                                thread_status != ThreadStatus::Stopped
 784                                                    && thread_status != ThreadStatus::Running,
 785                                            )
 786                                            .tooltip({
 787                                                let focus_handle = focus_handle.clone();
 788                                                let label = if capabilities
 789                                                    .supports_terminate_threads_request
 790                                                    .unwrap_or_default()
 791                                                {
 792                                                    "Terminate Thread"
 793                                                } else {
 794                                                    "Terminate All Threads"
 795                                                };
 796                                                move |window, cx| {
 797                                                    Tooltip::for_action_in(
 798                                                        label,
 799                                                        &Stop,
 800                                                        &focus_handle,
 801                                                        window,
 802                                                        cx,
 803                                                    )
 804                                                }
 805                                            }),
 806                                    )
 807                                    .child(
 808                                        IconButton::new("debug-disconnect", IconName::DebugDetach)
 809                                            .icon_size(IconSize::XSmall)
 810                                            .on_click(window.listener_for(
 811                                                &running_state,
 812                                                |this, _, _, cx| {
 813                                                    this.detach_client(cx);
 814                                                },
 815                                            ))
 816                                            .tooltip({
 817                                                let focus_handle = focus_handle.clone();
 818                                                move |window, cx| {
 819                                                    Tooltip::for_action_in(
 820                                                        "Detach",
 821                                                        &Detach,
 822                                                        &focus_handle,
 823                                                        window,
 824                                                        cx,
 825                                                    )
 826                                                }
 827                                            }),
 828                                    )
 829                                },
 830                            ),
 831                        )
 832                        .justify_around()
 833                        .when(is_side, |this| this.child(new_session_button())),
 834                )
 835                .child(
 836                    h_flex()
 837                        .gap_2()
 838                        .when(is_side, |this| this.justify_between())
 839                        .child(
 840                            h_flex().when_some(
 841                                active_session
 842                                    .as_ref()
 843                                    .map(|session| session.read(cx).running_state())
 844                                    .cloned(),
 845                                |this, running_state| {
 846                                    this.children({
 847                                        let running_state = running_state.clone();
 848                                        let threads =
 849                                            running_state.update(cx, |running_state, cx| {
 850                                                let session = running_state.session();
 851                                                session
 852                                                    .update(cx, |session, cx| session.threads(cx))
 853                                            });
 854
 855                                        self.render_thread_dropdown(
 856                                            &running_state,
 857                                            threads,
 858                                            window,
 859                                            cx,
 860                                        )
 861                                    })
 862                                    .when(!is_side, |this| this.gap_2().child(Divider::vertical()))
 863                                },
 864                            ),
 865                        )
 866                        .child(
 867                            h_flex()
 868                                .children(self.render_session_menu(
 869                                    self.active_session(),
 870                                    self.running_state(cx),
 871                                    window,
 872                                    cx,
 873                                ))
 874                                .when(!is_side, |this| this.child(new_session_button())),
 875                        ),
 876                ),
 877        )
 878    }
 879
 880    pub(crate) fn activate_pane_in_direction(
 881        &mut self,
 882        direction: SplitDirection,
 883        window: &mut Window,
 884        cx: &mut Context<Self>,
 885    ) {
 886        if let Some(session) = self.active_session() {
 887            session.update(cx, |session, cx| {
 888                session.running_state().update(cx, |running, cx| {
 889                    running.activate_pane_in_direction(direction, window, cx);
 890                })
 891            });
 892        }
 893    }
 894
 895    pub(crate) fn activate_item(
 896        &mut self,
 897        item: DebuggerPaneItem,
 898        window: &mut Window,
 899        cx: &mut Context<Self>,
 900    ) {
 901        if let Some(session) = self.active_session() {
 902            session.update(cx, |session, cx| {
 903                session.running_state().update(cx, |running, cx| {
 904                    running.activate_item(item, window, cx);
 905                });
 906            });
 907        }
 908    }
 909
 910    pub(crate) fn activate_session_by_id(
 911        &mut self,
 912        session_id: SessionId,
 913        window: &mut Window,
 914        cx: &mut Context<Self>,
 915    ) {
 916        if let Some(session) = self
 917            .sessions
 918            .iter()
 919            .find(|session| session.read(cx).session_id(cx) == session_id)
 920        {
 921            self.activate_session(session.clone(), window, cx);
 922        }
 923    }
 924
 925    pub(crate) fn activate_session(
 926        &mut self,
 927        session_item: Entity<DebugSession>,
 928        window: &mut Window,
 929        cx: &mut Context<Self>,
 930    ) {
 931        debug_assert!(self.sessions.contains(&session_item));
 932        session_item.focus_handle(cx).focus(window);
 933        session_item.update(cx, |this, cx| {
 934            this.running_state().update(cx, |this, cx| {
 935                this.go_to_selected_stack_frame(window, cx);
 936            });
 937        });
 938        self.active_session = Some(session_item);
 939        cx.notify();
 940    }
 941
 942    pub(crate) fn save_scenario(
 943        &self,
 944        scenario: &DebugScenario,
 945        worktree_id: WorktreeId,
 946        window: &mut Window,
 947        cx: &mut App,
 948    ) -> Task<Result<ProjectPath>> {
 949        self.workspace
 950            .update(cx, |workspace, cx| {
 951                let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
 952                    return Task::ready(Err(anyhow!("Couldn't get worktree path")));
 953                };
 954
 955                let serialized_scenario = serde_json::to_value(scenario);
 956
 957                cx.spawn_in(window, async move |workspace, cx| {
 958                    let serialized_scenario = serialized_scenario?;
 959                    let fs =
 960                        workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
 961
 962                    path.push(paths::local_settings_folder_relative_path());
 963                    if !fs.is_dir(path.as_path()).await {
 964                        fs.create_dir(path.as_path()).await?;
 965                    }
 966                    path.pop();
 967
 968                    path.push(paths::local_debug_file_relative_path());
 969                    let path = path.as_path();
 970
 971                    if !fs.is_file(path).await {
 972                        let content =
 973                            serde_json::to_string_pretty(&serde_json::Value::Array(vec![
 974                                serialized_scenario,
 975                            ]))?;
 976
 977                        fs.create_file(path, Default::default()).await?;
 978                        fs.save(path, &content.into(), Default::default()).await?;
 979                    } else {
 980                        let content = fs.load(path).await?;
 981                        let mut values = serde_json::from_str::<Vec<serde_json::Value>>(&content)?;
 982                        values.push(serialized_scenario);
 983                        fs.save(
 984                            path,
 985                            &serde_json::to_string_pretty(&values).map(Into::into)?,
 986                            Default::default(),
 987                        )
 988                        .await?;
 989                    }
 990
 991                    workspace.update(cx, |workspace, cx| {
 992                        workspace
 993                            .project()
 994                            .read(cx)
 995                            .project_path_for_absolute_path(&path, cx)
 996                            .context(
 997                                "Couldn't get project path for .zed/debug.json in active worktree",
 998                            )
 999                    })?
1000                })
1001            })
1002            .unwrap_or_else(|err| Task::ready(Err(err)))
1003    }
1004
1005    pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1006        self.thread_picker_menu_handle.toggle(window, cx);
1007    }
1008
1009    pub(crate) fn toggle_session_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1010        self.session_picker_menu_handle.toggle(window, cx);
1011    }
1012}
1013
1014async fn register_session_inner(
1015    this: &WeakEntity<DebugPanel>,
1016    session: Entity<Session>,
1017    cx: &mut AsyncWindowContext,
1018) -> Result<Entity<DebugSession>> {
1019    let adapter_name = session.read_with(cx, |session, _| session.adapter())?;
1020    this.update_in(cx, |_, window, cx| {
1021        cx.subscribe_in(
1022            &session,
1023            window,
1024            move |this, session, event: &SessionStateEvent, window, cx| match event {
1025                SessionStateEvent::Restart => {
1026                    this.handle_restart_request(session.clone(), window, cx);
1027                }
1028                SessionStateEvent::SpawnChildSession { request } => {
1029                    this.handle_start_debugging_request(request, session.clone(), window, cx);
1030                }
1031                _ => {}
1032            },
1033        )
1034        .detach();
1035    })
1036    .ok();
1037    let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
1038    let debug_session = this.update_in(cx, |this, window, cx| {
1039        this.sessions.retain(|session| {
1040            !session
1041                .read(cx)
1042                .running_state()
1043                .read(cx)
1044                .session()
1045                .read(cx)
1046                .is_terminated()
1047        });
1048
1049        let debug_session = DebugSession::running(
1050            this.project.clone(),
1051            this.workspace.clone(),
1052            session,
1053            cx.weak_entity(),
1054            serialized_layout,
1055            this.position(window, cx).axis(),
1056            window,
1057            cx,
1058        );
1059
1060        // We might want to make this an event subscription and only notify when a new thread is selected
1061        // This is used to filter the command menu correctly
1062        cx.observe(
1063            &debug_session.read(cx).running_state().clone(),
1064            |_, _, cx| cx.notify(),
1065        )
1066        .detach();
1067
1068        this.sessions.push(debug_session.clone());
1069
1070        debug_session
1071    })?;
1072    Ok(debug_session)
1073}
1074
1075impl EventEmitter<PanelEvent> for DebugPanel {}
1076impl EventEmitter<DebugPanelEvent> for DebugPanel {}
1077
1078impl Focusable for DebugPanel {
1079    fn focus_handle(&self, _: &App) -> FocusHandle {
1080        self.focus_handle.clone()
1081    }
1082}
1083
1084impl Panel for DebugPanel {
1085    fn persistent_name() -> &'static str {
1086        "DebugPanel"
1087    }
1088
1089    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1090        match DebuggerSettings::get_global(cx).dock {
1091            DebugPanelDockPosition::Left => DockPosition::Left,
1092            DebugPanelDockPosition::Bottom => DockPosition::Bottom,
1093            DebugPanelDockPosition::Right => DockPosition::Right,
1094        }
1095    }
1096
1097    fn position_is_valid(&self, _: DockPosition) -> bool {
1098        true
1099    }
1100
1101    fn set_position(
1102        &mut self,
1103        position: DockPosition,
1104        window: &mut Window,
1105        cx: &mut Context<Self>,
1106    ) {
1107        if position.axis() != self.position(window, cx).axis() {
1108            self.sessions.iter().for_each(|session_item| {
1109                session_item.update(cx, |item, cx| {
1110                    item.running_state()
1111                        .update(cx, |state, _| state.invert_axies())
1112                })
1113            });
1114        }
1115
1116        settings::update_settings_file::<DebuggerSettings>(
1117            self.fs.clone(),
1118            cx,
1119            move |settings, _| {
1120                let dock = match position {
1121                    DockPosition::Left => DebugPanelDockPosition::Left,
1122                    DockPosition::Bottom => DebugPanelDockPosition::Bottom,
1123                    DockPosition::Right => DebugPanelDockPosition::Right,
1124                };
1125                settings.dock = dock;
1126            },
1127        );
1128    }
1129
1130    fn size(&self, _window: &Window, _: &App) -> Pixels {
1131        self.size
1132    }
1133
1134    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
1135        self.size = size.unwrap_or(px(300.));
1136    }
1137
1138    fn remote_id() -> Option<proto::PanelId> {
1139        Some(proto::PanelId::DebugPanel)
1140    }
1141
1142    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
1143        Some(IconName::Debug)
1144    }
1145
1146    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1147        if DebuggerSettings::get_global(cx).button {
1148            Some("Debug Panel")
1149        } else {
1150            None
1151        }
1152    }
1153
1154    fn toggle_action(&self) -> Box<dyn Action> {
1155        Box::new(ToggleFocus)
1156    }
1157
1158    fn pane(&self) -> Option<Entity<Pane>> {
1159        None
1160    }
1161
1162    fn activation_priority(&self) -> u32 {
1163        9
1164    }
1165
1166    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1167}
1168
1169impl Render for DebugPanel {
1170    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1171        let has_sessions = self.sessions.len() > 0;
1172        let this = cx.weak_entity();
1173        debug_assert_eq!(has_sessions, self.active_session.is_some());
1174
1175        if self
1176            .active_session
1177            .as_ref()
1178            .map(|session| session.read(cx).running_state())
1179            .map(|state| state.read(cx).has_open_context_menu(cx))
1180            .unwrap_or(false)
1181        {
1182            self.context_menu.take();
1183        }
1184
1185        v_flex()
1186            .size_full()
1187            .key_context("DebugPanel")
1188            .child(h_flex().children(self.top_controls_strip(window, cx)))
1189            .track_focus(&self.focus_handle(cx))
1190            .on_action({
1191                let this = this.clone();
1192                move |_: &workspace::ActivatePaneLeft, window, cx| {
1193                    this.update(cx, |this, cx| {
1194                        this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1195                    })
1196                    .ok();
1197                }
1198            })
1199            .on_action({
1200                let this = this.clone();
1201                move |_: &workspace::ActivatePaneRight, window, cx| {
1202                    this.update(cx, |this, cx| {
1203                        this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1204                    })
1205                    .ok();
1206                }
1207            })
1208            .on_action({
1209                let this = this.clone();
1210                move |_: &workspace::ActivatePaneUp, window, cx| {
1211                    this.update(cx, |this, cx| {
1212                        this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1213                    })
1214                    .ok();
1215                }
1216            })
1217            .on_action({
1218                let this = this.clone();
1219                move |_: &workspace::ActivatePaneDown, window, cx| {
1220                    this.update(cx, |this, cx| {
1221                        this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1222                    })
1223                    .ok();
1224                }
1225            })
1226            .on_action({
1227                let this = this.clone();
1228                move |_: &FocusConsole, window, cx| {
1229                    this.update(cx, |this, cx| {
1230                        this.activate_item(DebuggerPaneItem::Console, window, cx);
1231                    })
1232                    .ok();
1233                }
1234            })
1235            .on_action({
1236                let this = this.clone();
1237                move |_: &FocusVariables, window, cx| {
1238                    this.update(cx, |this, cx| {
1239                        this.activate_item(DebuggerPaneItem::Variables, window, cx);
1240                    })
1241                    .ok();
1242                }
1243            })
1244            .on_action({
1245                let this = this.clone();
1246                move |_: &FocusBreakpointList, window, cx| {
1247                    this.update(cx, |this, cx| {
1248                        this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1249                    })
1250                    .ok();
1251                }
1252            })
1253            .on_action({
1254                let this = this.clone();
1255                move |_: &FocusFrames, window, cx| {
1256                    this.update(cx, |this, cx| {
1257                        this.activate_item(DebuggerPaneItem::Frames, window, cx);
1258                    })
1259                    .ok();
1260                }
1261            })
1262            .on_action({
1263                let this = this.clone();
1264                move |_: &FocusModules, window, cx| {
1265                    this.update(cx, |this, cx| {
1266                        this.activate_item(DebuggerPaneItem::Modules, window, cx);
1267                    })
1268                    .ok();
1269                }
1270            })
1271            .on_action({
1272                let this = this.clone();
1273                move |_: &FocusLoadedSources, window, cx| {
1274                    this.update(cx, |this, cx| {
1275                        this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1276                    })
1277                    .ok();
1278                }
1279            })
1280            .on_action({
1281                let this = this.clone();
1282                move |_: &FocusTerminal, window, cx| {
1283                    this.update(cx, |this, cx| {
1284                        this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1285                    })
1286                    .ok();
1287                }
1288            })
1289            .on_action({
1290                let this = this.clone();
1291                move |_: &ToggleThreadPicker, window, cx| {
1292                    this.update(cx, |this, cx| {
1293                        this.toggle_thread_picker(window, cx);
1294                    })
1295                    .ok();
1296                }
1297            })
1298            .on_action({
1299                let this = this.clone();
1300                move |_: &ToggleSessionPicker, window, cx| {
1301                    this.update(cx, |this, cx| {
1302                        this.toggle_session_picker(window, cx);
1303                    })
1304                    .ok();
1305                }
1306            })
1307            .when(self.active_session.is_some(), |this| {
1308                this.on_mouse_down(
1309                    MouseButton::Right,
1310                    cx.listener(|this, event: &MouseDownEvent, window, cx| {
1311                        if this
1312                            .active_session
1313                            .as_ref()
1314                            .map(|session| {
1315                                let state = session.read(cx).running_state();
1316                                state.read(cx).has_pane_at_position(event.position)
1317                            })
1318                            .unwrap_or(false)
1319                        {
1320                            this.deploy_context_menu(event.position, window, cx);
1321                        }
1322                    }),
1323                )
1324                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1325                    deferred(
1326                        anchored()
1327                            .position(*position)
1328                            .anchor(gpui::Corner::TopLeft)
1329                            .child(menu.clone()),
1330                    )
1331                    .with_priority(1)
1332                }))
1333            })
1334            .map(|this| {
1335                if has_sessions {
1336                    this.children(self.active_session.clone())
1337                } else {
1338                    this.child(
1339                        v_flex()
1340                            .h_full()
1341                            .gap_1()
1342                            .items_center()
1343                            .justify_center()
1344                            .child(
1345                                h_flex().child(
1346                                    Label::new("No Debugging Sessions")
1347                                        .size(LabelSize::Small)
1348                                        .color(Color::Muted),
1349                                ),
1350                            )
1351                            .child(
1352                                h_flex().flex_shrink().child(
1353                                    Button::new("spawn-new-session-empty-state", "New Session")
1354                                        .size(ButtonSize::Large)
1355                                        .on_click(|_, window, cx| {
1356                                            window.dispatch_action(crate::Start.boxed_clone(), cx);
1357                                        }),
1358                                ),
1359                            ),
1360                    )
1361                }
1362            })
1363            .into_any()
1364    }
1365}
1366
1367struct DebuggerProvider(Entity<DebugPanel>);
1368
1369impl workspace::DebuggerProvider for DebuggerProvider {
1370    fn start_session(
1371        &self,
1372        definition: DebugScenario,
1373        context: TaskContext,
1374        buffer: Option<Entity<Buffer>>,
1375        window: &mut Window,
1376        cx: &mut App,
1377    ) {
1378        self.0.update(cx, |_, cx| {
1379            cx.defer_in(window, |this, window, cx| {
1380                this.start_session(definition, context, buffer, None, window, cx);
1381            })
1382        })
1383    }
1384}