debugger_panel.rs

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