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