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, anyhow};
  10use command_palette_hooks::CommandPaletteFilter;
  11use dap::DebugRequest;
  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    fn start_from_definition(
 202        &mut self,
 203        definition: DebugTaskDefinition,
 204        window: &mut Window,
 205        cx: &mut Context<Self>,
 206    ) -> Task<Result<()>> {
 207        cx.spawn_in(window, async move |this, cx| {
 208            let dap_store = this.update(cx, |this, cx| this.project.read(cx).dap_store())?;
 209            let (session, task) = dap_store.update(cx, |dap_store, cx| {
 210                let session = dap_store.new_session(definition, None, cx);
 211
 212                (session.clone(), dap_store.boot_session(session, cx))
 213            })?;
 214            Self::register_session(this.clone(), session.clone(), cx).await?;
 215
 216            if let Err(e) = task.await {
 217                this.update(cx, |this, cx| {
 218                    this.workspace
 219                        .update(cx, |workspace, cx| {
 220                            workspace.show_error(&e, cx);
 221                        })
 222                        .ok();
 223                })
 224                .ok();
 225
 226                session
 227                    .update(cx, |session, cx| session.shutdown(cx))?
 228                    .await;
 229            }
 230
 231            anyhow::Ok(())
 232        })
 233    }
 234
 235    pub fn start_session(
 236        &mut self,
 237        scenario: DebugScenario,
 238        task_context: TaskContext,
 239        active_buffer: Option<Entity<Buffer>>,
 240        window: &mut Window,
 241        cx: &mut Context<Self>,
 242    ) {
 243        cx.spawn_in(window, async move |this, cx| {
 244            let definition = this
 245                .update_in(cx, |this, window, cx| {
 246                    this.resolve_scenario(scenario, task_context, active_buffer, window, cx)
 247                })?
 248                .await?;
 249            this.update_in(cx, |this, window, cx| {
 250                this.start_from_definition(definition, window, cx)
 251            })?
 252            .await
 253        })
 254        .detach_and_log_err(cx);
 255    }
 256
 257    async fn register_session(
 258        this: WeakEntity<Self>,
 259        session: Entity<Session>,
 260        cx: &mut AsyncWindowContext,
 261    ) -> Result<()> {
 262        let adapter_name = session.update(cx, |session, _| session.adapter_name())?;
 263        this.update_in(cx, |_, window, cx| {
 264            cx.subscribe_in(
 265                &session,
 266                window,
 267                move |this, session, event: &SessionStateEvent, window, cx| match event {
 268                    SessionStateEvent::Restart => {
 269                        let mut curr_session = session.clone();
 270                        while let Some(parent_session) = curr_session
 271                            .read_with(cx, |session, _| session.parent_session().cloned())
 272                        {
 273                            curr_session = parent_session;
 274                        }
 275
 276                        let definition = curr_session.update(cx, |session, _| session.definition());
 277                        let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
 278
 279                        cx.spawn_in(window, async move |this, cx| {
 280                            task.await;
 281
 282                            this.update_in(cx, |this, window, cx| {
 283                                this.start_from_definition(definition, window, cx)
 284                            })?
 285                            .await
 286                        })
 287                        .detach_and_log_err(cx);
 288                    }
 289                    SessionStateEvent::SpawnChildSession { request } => {
 290                        this.handle_start_debugging_request(request, session.clone(), window, cx);
 291                    }
 292                    _ => {}
 293                },
 294            )
 295            .detach();
 296        })
 297        .ok();
 298
 299        let serialized_layout = persistence::get_serialized_pane_layout(adapter_name).await;
 300
 301        let workspace = this.update_in(cx, |this, window, cx| {
 302            this.sessions.retain(|session| {
 303                session
 304                    .read(cx)
 305                    .running_state()
 306                    .read(cx)
 307                    .session()
 308                    .read(cx)
 309                    .is_terminated()
 310            });
 311
 312            let session_item = DebugSession::running(
 313                this.project.clone(),
 314                this.workspace.clone(),
 315                session,
 316                cx.weak_entity(),
 317                serialized_layout,
 318                window,
 319                cx,
 320            );
 321
 322            // We might want to make this an event subscription and only notify when a new thread is selected
 323            // This is used to filter the command menu correctly
 324            cx.observe(
 325                &session_item.read(cx).running_state().clone(),
 326                |_, _, cx| cx.notify(),
 327            )
 328            .detach();
 329
 330            this.sessions.push(session_item.clone());
 331            this.activate_session(session_item, window, cx);
 332            this.workspace.clone()
 333        })?;
 334
 335        workspace.update_in(cx, |workspace, window, cx| {
 336            workspace.focus_panel::<Self>(window, cx);
 337        })?;
 338        Ok(())
 339    }
 340
 341    pub fn handle_start_debugging_request(
 342        &mut self,
 343        request: &StartDebuggingRequestArguments,
 344        parent_session: Entity<Session>,
 345        window: &mut Window,
 346        cx: &mut Context<Self>,
 347    ) {
 348        let Some(worktree) = parent_session.read(cx).worktree() else {
 349            log::error!("Attempted to start a child session from non local debug session");
 350            return;
 351        };
 352
 353        let dap_store_handle = self.project.read(cx).dap_store().clone();
 354        let breakpoint_store = self.project.read(cx).breakpoint_store();
 355        let definition = parent_session.read(cx).definition().clone();
 356        let mut binary = parent_session.read(cx).binary().clone();
 357        binary.request_args = request.clone();
 358
 359        cx.spawn_in(window, async move |this, cx| {
 360            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 361                let session =
 362                    dap_store.new_session(definition.clone(), Some(parent_session.clone()), cx);
 363
 364                let task = session.update(cx, |session, cx| {
 365                    session.boot(
 366                        binary,
 367                        worktree,
 368                        breakpoint_store,
 369                        dap_store_handle.downgrade(),
 370                        cx,
 371                    )
 372                });
 373                (session, task)
 374            })?;
 375
 376            match task.await {
 377                Err(e) => {
 378                    this.update(cx, |this, cx| {
 379                        this.workspace
 380                            .update(cx, |workspace, cx| {
 381                                workspace.show_error(&e, cx);
 382                            })
 383                            .ok();
 384                    })
 385                    .ok();
 386
 387                    session
 388                        .update(cx, |session, cx| session.shutdown(cx))?
 389                        .await;
 390                }
 391                Ok(_) => Self::register_session(this, session, cx).await?,
 392            }
 393
 394            anyhow::Ok(())
 395        })
 396        .detach_and_log_err(cx);
 397    }
 398
 399    pub fn active_session(&self) -> Option<Entity<DebugSession>> {
 400        self.active_session.clone()
 401    }
 402
 403    pub fn resolve_scenario(
 404        &self,
 405        scenario: DebugScenario,
 406
 407        task_context: TaskContext,
 408        buffer: Option<Entity<Buffer>>,
 409        window: &Window,
 410        cx: &mut Context<Self>,
 411    ) -> Task<Result<DebugTaskDefinition>> {
 412        let project = self.project.read(cx);
 413        let dap_store = project.dap_store().downgrade();
 414        let task_store = project.task_store().downgrade();
 415        let workspace = self.workspace.clone();
 416        cx.spawn_in(window, async move |_, cx| {
 417            let DebugScenario {
 418                adapter,
 419                label,
 420                build,
 421                request,
 422                initialize_args,
 423                tcp_connection,
 424                stop_on_entry,
 425            } = scenario;
 426            let request = if let Some(mut request) = request {
 427                // Resolve task variables within the request.
 428                if let DebugRequest::Launch(_) = &mut request {}
 429
 430                request
 431            } else if let Some(build) = build {
 432                let Some(task) = task_store.update(cx, |this, cx| {
 433                    this.task_inventory().and_then(|inventory| {
 434                        inventory
 435                            .read(cx)
 436                            .task_template_by_label(buffer, &build, cx)
 437                    })
 438                })?
 439                else {
 440                    anyhow::bail!("Couldn't find task template for {:?}", build)
 441                };
 442                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
 443                    anyhow::bail!("Could not resolve task variables within a debug scenario");
 444                };
 445
 446                let run_build = workspace.update_in(cx, |workspace, window, cx| {
 447                    workspace.spawn_in_terminal(task.resolved.clone(), window, cx)
 448                })?;
 449
 450                let exit_status = run_build.await?;
 451                if !exit_status.success() {
 452                    anyhow::bail!("Build failed");
 453                }
 454
 455                dap_store
 456                    .update(cx, |this, cx| this.run_debug_locator(task.resolved, cx))?
 457                    .await?
 458            } else {
 459                return Err(anyhow!("No request or build provided"));
 460            };
 461            Ok(DebugTaskDefinition {
 462                label,
 463                adapter,
 464                request,
 465                initialize_args,
 466                stop_on_entry,
 467                tcp_connection,
 468            })
 469        })
 470    }
 471
 472    fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
 473        let Some(session) = self
 474            .sessions
 475            .iter()
 476            .find(|other| entity_id == other.entity_id())
 477            .cloned()
 478        else {
 479            return;
 480        };
 481        session.update(cx, |this, cx| {
 482            this.running_state().update(cx, |this, cx| {
 483                this.serialize_layout(window, cx);
 484            });
 485        });
 486        let session_id = session.update(cx, |this, cx| this.session_id(cx));
 487        let should_prompt = self
 488            .project
 489            .update(cx, |this, cx| {
 490                let session = this.dap_store().read(cx).session_by_id(session_id);
 491                session.map(|session| !session.read(cx).is_terminated())
 492            })
 493            .unwrap_or_default();
 494
 495        cx.spawn_in(window, async move |this, cx| {
 496            if should_prompt {
 497                let response = cx.prompt(
 498                    gpui::PromptLevel::Warning,
 499                    "This Debug Session is still running. Are you sure you want to terminate it?",
 500                    None,
 501                    &["Yes", "No"],
 502                );
 503                if response.await == Ok(1) {
 504                    return;
 505                }
 506            }
 507            session.update(cx, |session, cx| session.shutdown(cx)).ok();
 508            this.update(cx, |this, cx| {
 509                this.sessions.retain(|other| entity_id != other.entity_id());
 510
 511                if let Some(active_session_id) = this
 512                    .active_session
 513                    .as_ref()
 514                    .map(|session| session.entity_id())
 515                {
 516                    if active_session_id == entity_id {
 517                        this.active_session = this.sessions.first().cloned();
 518                    }
 519                }
 520                cx.notify()
 521            })
 522            .ok();
 523        })
 524        .detach();
 525    }
 526    fn sessions_drop_down_menu(
 527        &self,
 528        active_session: &Entity<DebugSession>,
 529        window: &mut Window,
 530        cx: &mut Context<Self>,
 531    ) -> DropdownMenu {
 532        let sessions = self.sessions.clone();
 533        let weak = cx.weak_entity();
 534        let label = active_session.read(cx).label_element(cx);
 535
 536        DropdownMenu::new_with_element(
 537            "debugger-session-list",
 538            label,
 539            ContextMenu::build(window, cx, move |mut this, _, cx| {
 540                let context_menu = cx.weak_entity();
 541                for session in sessions.into_iter() {
 542                    let weak_session = session.downgrade();
 543                    let weak_session_id = weak_session.entity_id();
 544
 545                    this = this.custom_entry(
 546                        {
 547                            let weak = weak.clone();
 548                            let context_menu = context_menu.clone();
 549                            move |_, cx| {
 550                                weak_session
 551                                    .read_with(cx, |session, cx| {
 552                                        let context_menu = context_menu.clone();
 553                                        let id: SharedString =
 554                                            format!("debug-session-{}", session.session_id(cx).0)
 555                                                .into();
 556                                        h_flex()
 557                                            .w_full()
 558                                            .group(id.clone())
 559                                            .justify_between()
 560                                            .child(session.label_element(cx))
 561                                            .child(
 562                                                IconButton::new(
 563                                                    "close-debug-session",
 564                                                    IconName::Close,
 565                                                )
 566                                                .visible_on_hover(id.clone())
 567                                                .icon_size(IconSize::Small)
 568                                                .on_click({
 569                                                    let weak = weak.clone();
 570                                                    move |_, window, cx| {
 571                                                        weak.update(cx, |panel, cx| {
 572                                                            panel.close_session(
 573                                                                weak_session_id,
 574                                                                window,
 575                                                                cx,
 576                                                            );
 577                                                        })
 578                                                        .ok();
 579                                                        context_menu
 580                                                            .update(cx, |this, cx| {
 581                                                                this.cancel(
 582                                                                    &Default::default(),
 583                                                                    window,
 584                                                                    cx,
 585                                                                );
 586                                                            })
 587                                                            .ok();
 588                                                    }
 589                                                }),
 590                                            )
 591                                            .into_any_element()
 592                                    })
 593                                    .unwrap_or_else(|_| div().into_any_element())
 594                            }
 595                        },
 596                        {
 597                            let weak = weak.clone();
 598                            move |window, cx| {
 599                                weak.update(cx, |panel, cx| {
 600                                    panel.activate_session(session.clone(), window, cx);
 601                                })
 602                                .ok();
 603                            }
 604                        },
 605                    );
 606                }
 607                this
 608            }),
 609        )
 610    }
 611
 612    fn deploy_context_menu(
 613        &mut self,
 614        position: Point<Pixels>,
 615        window: &mut Window,
 616        cx: &mut Context<Self>,
 617    ) {
 618        if let Some(running_state) = self
 619            .active_session
 620            .as_ref()
 621            .map(|session| session.read(cx).running_state().clone())
 622        {
 623            let pane_items_status = running_state.read(cx).pane_items_status(cx);
 624            let this = cx.weak_entity();
 625
 626            let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
 627                for (item_kind, is_visible) in pane_items_status.into_iter() {
 628                    menu = menu.toggleable_entry(item_kind, is_visible, IconPosition::End, None, {
 629                        let this = this.clone();
 630                        move |window, cx| {
 631                            this.update(cx, |this, cx| {
 632                                if let Some(running_state) = this
 633                                    .active_session
 634                                    .as_ref()
 635                                    .map(|session| session.read(cx).running_state().clone())
 636                                {
 637                                    running_state.update(cx, |state, cx| {
 638                                        if is_visible {
 639                                            state.remove_pane_item(item_kind, window, cx);
 640                                        } else {
 641                                            state.add_pane_item(item_kind, position, window, cx);
 642                                        }
 643                                    })
 644                                }
 645                            })
 646                            .ok();
 647                        }
 648                    });
 649                }
 650
 651                menu
 652            });
 653
 654            window.focus(&context_menu.focus_handle(cx));
 655            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
 656                this.context_menu.take();
 657                cx.notify();
 658            });
 659            self.context_menu = Some((context_menu, position, subscription));
 660        }
 661    }
 662
 663    fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
 664        let active_session = self.active_session.clone();
 665        let focus_handle = self.focus_handle.clone();
 666
 667        Some(
 668            h_flex()
 669                .border_b_1()
 670                .border_color(cx.theme().colors().border)
 671                .p_1()
 672                .justify_between()
 673                .w_full()
 674                .child(
 675                    h_flex().gap_2().w_full().when_some(
 676                        active_session
 677                            .as_ref()
 678                            .map(|session| session.read(cx).running_state()),
 679                        |this, running_session| {
 680                            let thread_status = running_session
 681                                .read(cx)
 682                                .thread_status(cx)
 683                                .unwrap_or(project::debugger::session::ThreadStatus::Exited);
 684                            let capabilities = running_session.read(cx).capabilities(cx);
 685                            this.map(|this| {
 686                                if thread_status == ThreadStatus::Running {
 687                                    this.child(
 688                                        IconButton::new("debug-pause", IconName::DebugPause)
 689                                            .icon_size(IconSize::XSmall)
 690                                            .shape(ui::IconButtonShape::Square)
 691                                            .on_click(window.listener_for(
 692                                                &running_session,
 693                                                |this, _, _window, cx| {
 694                                                    this.pause_thread(cx);
 695                                                },
 696                                            ))
 697                                            .tooltip({
 698                                                let focus_handle = focus_handle.clone();
 699                                                move |window, cx| {
 700                                                    Tooltip::for_action_in(
 701                                                        "Pause program",
 702                                                        &Pause,
 703                                                        &focus_handle,
 704                                                        window,
 705                                                        cx,
 706                                                    )
 707                                                }
 708                                            }),
 709                                    )
 710                                } else {
 711                                    this.child(
 712                                        IconButton::new("debug-continue", IconName::DebugContinue)
 713                                            .icon_size(IconSize::XSmall)
 714                                            .shape(ui::IconButtonShape::Square)
 715                                            .on_click(window.listener_for(
 716                                                &running_session,
 717                                                |this, _, _window, cx| this.continue_thread(cx),
 718                                            ))
 719                                            .disabled(thread_status != ThreadStatus::Stopped)
 720                                            .tooltip({
 721                                                let focus_handle = focus_handle.clone();
 722                                                move |window, cx| {
 723                                                    Tooltip::for_action_in(
 724                                                        "Continue program",
 725                                                        &Continue,
 726                                                        &focus_handle,
 727                                                        window,
 728                                                        cx,
 729                                                    )
 730                                                }
 731                                            }),
 732                                    )
 733                                }
 734                            })
 735                            .child(
 736                                IconButton::new("debug-step-over", IconName::ArrowRight)
 737                                    .icon_size(IconSize::XSmall)
 738                                    .shape(ui::IconButtonShape::Square)
 739                                    .on_click(window.listener_for(
 740                                        &running_session,
 741                                        |this, _, _window, cx| {
 742                                            this.step_over(cx);
 743                                        },
 744                                    ))
 745                                    .disabled(thread_status != ThreadStatus::Stopped)
 746                                    .tooltip({
 747                                        let focus_handle = focus_handle.clone();
 748                                        move |window, cx| {
 749                                            Tooltip::for_action_in(
 750                                                "Step over",
 751                                                &StepOver,
 752                                                &focus_handle,
 753                                                window,
 754                                                cx,
 755                                            )
 756                                        }
 757                                    }),
 758                            )
 759                            .child(
 760                                IconButton::new("debug-step-out", IconName::ArrowUpRight)
 761                                    .icon_size(IconSize::XSmall)
 762                                    .shape(ui::IconButtonShape::Square)
 763                                    .on_click(window.listener_for(
 764                                        &running_session,
 765                                        |this, _, _window, cx| {
 766                                            this.step_out(cx);
 767                                        },
 768                                    ))
 769                                    .disabled(thread_status != ThreadStatus::Stopped)
 770                                    .tooltip({
 771                                        let focus_handle = focus_handle.clone();
 772                                        move |window, cx| {
 773                                            Tooltip::for_action_in(
 774                                                "Step out",
 775                                                &StepOut,
 776                                                &focus_handle,
 777                                                window,
 778                                                cx,
 779                                            )
 780                                        }
 781                                    }),
 782                            )
 783                            .child(
 784                                IconButton::new("debug-step-into", IconName::ArrowDownRight)
 785                                    .icon_size(IconSize::XSmall)
 786                                    .shape(ui::IconButtonShape::Square)
 787                                    .on_click(window.listener_for(
 788                                        &running_session,
 789                                        |this, _, _window, cx| {
 790                                            this.step_in(cx);
 791                                        },
 792                                    ))
 793                                    .disabled(thread_status != ThreadStatus::Stopped)
 794                                    .tooltip({
 795                                        let focus_handle = focus_handle.clone();
 796                                        move |window, cx| {
 797                                            Tooltip::for_action_in(
 798                                                "Step in",
 799                                                &StepInto,
 800                                                &focus_handle,
 801                                                window,
 802                                                cx,
 803                                            )
 804                                        }
 805                                    }),
 806                            )
 807                            .child(Divider::vertical())
 808                            .child(
 809                                IconButton::new(
 810                                    "debug-enable-breakpoint",
 811                                    IconName::DebugDisabledBreakpoint,
 812                                )
 813                                .icon_size(IconSize::XSmall)
 814                                .shape(ui::IconButtonShape::Square)
 815                                .disabled(thread_status != ThreadStatus::Stopped),
 816                            )
 817                            .child(
 818                                IconButton::new("debug-disable-breakpoint", IconName::CircleOff)
 819                                    .icon_size(IconSize::XSmall)
 820                                    .shape(ui::IconButtonShape::Square)
 821                                    .disabled(thread_status != ThreadStatus::Stopped),
 822                            )
 823                            .child(
 824                                IconButton::new("debug-disable-all-breakpoints", IconName::BugOff)
 825                                    .icon_size(IconSize::XSmall)
 826                                    .shape(ui::IconButtonShape::Square)
 827                                    .disabled(
 828                                        thread_status == ThreadStatus::Exited
 829                                            || thread_status == ThreadStatus::Ended,
 830                                    )
 831                                    .on_click(window.listener_for(
 832                                        &running_session,
 833                                        |this, _, _window, cx| {
 834                                            this.toggle_ignore_breakpoints(cx);
 835                                        },
 836                                    ))
 837                                    .tooltip({
 838                                        let focus_handle = focus_handle.clone();
 839                                        move |window, cx| {
 840                                            Tooltip::for_action_in(
 841                                                "Disable all breakpoints",
 842                                                &ToggleIgnoreBreakpoints,
 843                                                &focus_handle,
 844                                                window,
 845                                                cx,
 846                                            )
 847                                        }
 848                                    }),
 849                            )
 850                            .child(Divider::vertical())
 851                            .child(
 852                                IconButton::new("debug-restart", IconName::DebugRestart)
 853                                    .icon_size(IconSize::XSmall)
 854                                    .on_click(window.listener_for(
 855                                        &running_session,
 856                                        |this, _, _window, cx| {
 857                                            this.restart_session(cx);
 858                                        },
 859                                    ))
 860                                    .tooltip({
 861                                        let focus_handle = focus_handle.clone();
 862                                        move |window, cx| {
 863                                            Tooltip::for_action_in(
 864                                                "Restart",
 865                                                &Restart,
 866                                                &focus_handle,
 867                                                window,
 868                                                cx,
 869                                            )
 870                                        }
 871                                    }),
 872                            )
 873                            .child(
 874                                IconButton::new("debug-stop", IconName::Power)
 875                                    .icon_size(IconSize::XSmall)
 876                                    .on_click(window.listener_for(
 877                                        &running_session,
 878                                        |this, _, _window, cx| {
 879                                            this.stop_thread(cx);
 880                                        },
 881                                    ))
 882                                    .disabled(
 883                                        thread_status != ThreadStatus::Stopped
 884                                            && thread_status != ThreadStatus::Running,
 885                                    )
 886                                    .tooltip({
 887                                        let focus_handle = focus_handle.clone();
 888                                        let label = if capabilities
 889                                            .supports_terminate_threads_request
 890                                            .unwrap_or_default()
 891                                        {
 892                                            "Terminate Thread"
 893                                        } else {
 894                                            "Terminate All Threads"
 895                                        };
 896                                        move |window, cx| {
 897                                            Tooltip::for_action_in(
 898                                                label,
 899                                                &Stop,
 900                                                &focus_handle,
 901                                                window,
 902                                                cx,
 903                                            )
 904                                        }
 905                                    }),
 906                            )
 907                        },
 908                    ),
 909                )
 910                .child(
 911                    h_flex()
 912                        .gap_2()
 913                        .when_some(
 914                            active_session
 915                                .as_ref()
 916                                .map(|session| session.read(cx).running_state())
 917                                .cloned(),
 918                            |this, session| {
 919                                this.child(
 920                                    session.update(cx, |this, cx| this.thread_dropdown(window, cx)),
 921                                )
 922                                .child(Divider::vertical())
 923                            },
 924                        )
 925                        .when_some(active_session.as_ref(), |this, session| {
 926                            let context_menu = self.sessions_drop_down_menu(session, window, cx);
 927                            this.child(context_menu).child(Divider::vertical())
 928                        })
 929                        .child(
 930                            IconButton::new("debug-new-session", IconName::Plus)
 931                                .icon_size(IconSize::Small)
 932                                .on_click({
 933                                    let workspace = self.workspace.clone();
 934                                    let weak_panel = cx.weak_entity();
 935                                    let past_debug_definition = self.past_debug_definition.clone();
 936                                    move |_, window, cx| {
 937                                        let weak_panel = weak_panel.clone();
 938                                        let past_debug_definition = past_debug_definition.clone();
 939
 940                                        let _ = workspace.update(cx, |this, cx| {
 941                                            let workspace = cx.weak_entity();
 942                                            this.toggle_modal(window, cx, |window, cx| {
 943                                                NewSessionModal::new(
 944                                                    past_debug_definition,
 945                                                    weak_panel,
 946                                                    workspace,
 947                                                    window,
 948                                                    cx,
 949                                                )
 950                                            });
 951                                        });
 952                                    }
 953                                })
 954                                .tooltip({
 955                                    let focus_handle = focus_handle.clone();
 956                                    move |window, cx| {
 957                                        Tooltip::for_action_in(
 958                                            "New Debug Session",
 959                                            &CreateDebuggingSession,
 960                                            &focus_handle,
 961                                            window,
 962                                            cx,
 963                                        )
 964                                    }
 965                                }),
 966                        ),
 967                ),
 968        )
 969    }
 970
 971    fn activate_pane_in_direction(
 972        &mut self,
 973        direction: SplitDirection,
 974        window: &mut Window,
 975        cx: &mut Context<Self>,
 976    ) {
 977        if let Some(session) = self.active_session() {
 978            session.update(cx, |session, cx| {
 979                session.running_state().update(cx, |running, cx| {
 980                    running.activate_pane_in_direction(direction, window, cx);
 981                })
 982            });
 983        }
 984    }
 985
 986    fn activate_item(
 987        &mut self,
 988        item: DebuggerPaneItem,
 989        window: &mut Window,
 990        cx: &mut Context<Self>,
 991    ) {
 992        if let Some(session) = self.active_session() {
 993            session.update(cx, |session, cx| {
 994                session.running_state().update(cx, |running, cx| {
 995                    running.activate_item(item, window, cx);
 996                });
 997            });
 998        }
 999    }
1000
1001    fn activate_session(
1002        &mut self,
1003        session_item: Entity<DebugSession>,
1004        window: &mut Window,
1005        cx: &mut Context<Self>,
1006    ) {
1007        debug_assert!(self.sessions.contains(&session_item));
1008        session_item.focus_handle(cx).focus(window);
1009        session_item.update(cx, |this, cx| {
1010            this.running_state().update(cx, |this, cx| {
1011                this.go_to_selected_stack_frame(window, cx);
1012            });
1013        });
1014        self.active_session = Some(session_item);
1015        cx.notify();
1016    }
1017}
1018
1019impl EventEmitter<PanelEvent> for DebugPanel {}
1020impl EventEmitter<DebugPanelEvent> for DebugPanel {}
1021
1022impl Focusable for DebugPanel {
1023    fn focus_handle(&self, _: &App) -> FocusHandle {
1024        self.focus_handle.clone()
1025    }
1026}
1027
1028impl Panel for DebugPanel {
1029    fn persistent_name() -> &'static str {
1030        "DebugPanel"
1031    }
1032
1033    fn position(&self, _window: &Window, _cx: &App) -> DockPosition {
1034        DockPosition::Bottom
1035    }
1036
1037    fn position_is_valid(&self, position: DockPosition) -> bool {
1038        position == DockPosition::Bottom
1039    }
1040
1041    fn set_position(
1042        &mut self,
1043        _position: DockPosition,
1044        _window: &mut Window,
1045        _cx: &mut Context<Self>,
1046    ) {
1047    }
1048
1049    fn size(&self, _window: &Window, _: &App) -> Pixels {
1050        self.size
1051    }
1052
1053    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
1054        self.size = size.unwrap();
1055    }
1056
1057    fn remote_id() -> Option<proto::PanelId> {
1058        Some(proto::PanelId::DebugPanel)
1059    }
1060
1061    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
1062        Some(IconName::Debug)
1063    }
1064
1065    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1066        if DebuggerSettings::get_global(cx).button {
1067            Some("Debug Panel")
1068        } else {
1069            None
1070        }
1071    }
1072
1073    fn toggle_action(&self) -> Box<dyn Action> {
1074        Box::new(ToggleFocus)
1075    }
1076
1077    fn pane(&self) -> Option<Entity<Pane>> {
1078        None
1079    }
1080
1081    fn activation_priority(&self) -> u32 {
1082        9
1083    }
1084
1085    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1086}
1087
1088impl Render for DebugPanel {
1089    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1090        let has_sessions = self.sessions.len() > 0;
1091        let this = cx.weak_entity();
1092        debug_assert_eq!(has_sessions, self.active_session.is_some());
1093
1094        if self
1095            .active_session
1096            .as_ref()
1097            .map(|session| session.read(cx).running_state())
1098            .map(|state| state.read(cx).has_open_context_menu(cx))
1099            .unwrap_or(false)
1100        {
1101            self.context_menu.take();
1102        }
1103
1104        v_flex()
1105            .size_full()
1106            .key_context("DebugPanel")
1107            .child(h_flex().children(self.top_controls_strip(window, cx)))
1108            .track_focus(&self.focus_handle(cx))
1109            .on_action({
1110                let this = this.clone();
1111                move |_: &workspace::ActivatePaneLeft, window, cx| {
1112                    this.update(cx, |this, cx| {
1113                        this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1114                    })
1115                    .ok();
1116                }
1117            })
1118            .on_action({
1119                let this = this.clone();
1120                move |_: &workspace::ActivatePaneRight, window, cx| {
1121                    this.update(cx, |this, cx| {
1122                        this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1123                    })
1124                    .ok();
1125                }
1126            })
1127            .on_action({
1128                let this = this.clone();
1129                move |_: &workspace::ActivatePaneUp, window, cx| {
1130                    this.update(cx, |this, cx| {
1131                        this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1132                    })
1133                    .ok();
1134                }
1135            })
1136            .on_action({
1137                let this = this.clone();
1138                move |_: &workspace::ActivatePaneDown, window, cx| {
1139                    this.update(cx, |this, cx| {
1140                        this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1141                    })
1142                    .ok();
1143                }
1144            })
1145            .on_action({
1146                let this = this.clone();
1147                move |_: &FocusConsole, window, cx| {
1148                    this.update(cx, |this, cx| {
1149                        this.activate_item(DebuggerPaneItem::Console, window, cx);
1150                    })
1151                    .ok();
1152                }
1153            })
1154            .on_action({
1155                let this = this.clone();
1156                move |_: &FocusVariables, window, cx| {
1157                    this.update(cx, |this, cx| {
1158                        this.activate_item(DebuggerPaneItem::Variables, window, cx);
1159                    })
1160                    .ok();
1161                }
1162            })
1163            .on_action({
1164                let this = this.clone();
1165                move |_: &FocusBreakpointList, window, cx| {
1166                    this.update(cx, |this, cx| {
1167                        this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1168                    })
1169                    .ok();
1170                }
1171            })
1172            .on_action({
1173                let this = this.clone();
1174                move |_: &FocusFrames, window, cx| {
1175                    this.update(cx, |this, cx| {
1176                        this.activate_item(DebuggerPaneItem::Frames, window, cx);
1177                    })
1178                    .ok();
1179                }
1180            })
1181            .on_action({
1182                let this = this.clone();
1183                move |_: &FocusModules, window, cx| {
1184                    this.update(cx, |this, cx| {
1185                        this.activate_item(DebuggerPaneItem::Modules, window, cx);
1186                    })
1187                    .ok();
1188                }
1189            })
1190            .on_action({
1191                let this = this.clone();
1192                move |_: &FocusLoadedSources, window, cx| {
1193                    this.update(cx, |this, cx| {
1194                        this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1195                    })
1196                    .ok();
1197                }
1198            })
1199            .on_action({
1200                let this = this.clone();
1201                move |_: &FocusTerminal, window, cx| {
1202                    this.update(cx, |this, cx| {
1203                        this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1204                    })
1205                    .ok();
1206                }
1207            })
1208            .when(self.active_session.is_some(), |this| {
1209                this.on_mouse_down(
1210                    MouseButton::Right,
1211                    cx.listener(|this, event: &MouseDownEvent, window, cx| {
1212                        if this
1213                            .active_session
1214                            .as_ref()
1215                            .map(|session| {
1216                                let state = session.read(cx).running_state();
1217                                state.read(cx).has_pane_at_position(event.position)
1218                            })
1219                            .unwrap_or(false)
1220                        {
1221                            this.deploy_context_menu(event.position, window, cx);
1222                        }
1223                    }),
1224                )
1225                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1226                    deferred(
1227                        anchored()
1228                            .position(*position)
1229                            .anchor(gpui::Corner::TopLeft)
1230                            .child(menu.clone()),
1231                    )
1232                    .with_priority(1)
1233                }))
1234            })
1235            .map(|this| {
1236                if has_sessions {
1237                    this.children(self.active_session.clone())
1238                } else {
1239                    this.child(
1240                        v_flex()
1241                            .h_full()
1242                            .gap_1()
1243                            .items_center()
1244                            .justify_center()
1245                            .child(
1246                                h_flex().child(
1247                                    Label::new("No Debugging Sessions")
1248                                        .size(LabelSize::Small)
1249                                        .color(Color::Muted),
1250                                ),
1251                            )
1252                            .child(
1253                                h_flex().flex_shrink().child(
1254                                    Button::new("spawn-new-session-empty-state", "New Session")
1255                                        .size(ButtonSize::Large)
1256                                        .on_click(|_, window, cx| {
1257                                            window.dispatch_action(
1258                                                CreateDebuggingSession.boxed_clone(),
1259                                                cx,
1260                                            );
1261                                        }),
1262                                ),
1263                            ),
1264                    )
1265                }
1266            })
1267            .into_any()
1268    }
1269}
1270
1271struct DebuggerProvider(Entity<DebugPanel>);
1272
1273impl workspace::DebuggerProvider for DebuggerProvider {
1274    fn start_session(
1275        &self,
1276        definition: DebugScenario,
1277        context: TaskContext,
1278        buffer: Option<Entity<Buffer>>,
1279        window: &mut Window,
1280        cx: &mut App,
1281    ) {
1282        self.0.update(cx, |_, cx| {
1283            cx.defer_in(window, |this, window, cx| {
1284                this.start_session(definition, context, buffer, window, cx);
1285            })
1286        })
1287    }
1288}