debugger_panel.rs

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