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