debugger_panel.rs

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