debugger_panel.rs

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