debugger_panel.rs

   1use crate::persistence::DebuggerPaneItem;
   2use crate::session::DebugSession;
   3use crate::{
   4    ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
   5    FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart, StepBack,
   6    StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence,
   7};
   8use anyhow::Result;
   9use command_palette_hooks::CommandPaletteFilter;
  10use dap::adapters::DebugAdapterName;
  11use dap::debugger_settings::DebugPanelDockPosition;
  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::{Fs, WorktreeId};
  26use project::{Project, debugger::session::ThreadStatus};
  27use rpc::proto::{self};
  28use settings::Settings;
  29use std::any::TypeId;
  30use std::sync::Arc;
  31use task::{DebugScenario, TaskContext};
  32use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
  33use workspace::SplitDirection;
  34use workspace::{
  35    Pane, Workspace,
  36    dock::{DockPosition, Panel, PanelEvent},
  37};
  38
  39pub enum DebugPanelEvent {
  40    Exited(SessionId),
  41    Terminated(SessionId),
  42    Stopped {
  43        client_id: SessionId,
  44        event: StoppedEvent,
  45        go_to_stack_frame: bool,
  46    },
  47    Thread((SessionId, ThreadEvent)),
  48    Continued((SessionId, ContinuedEvent)),
  49    Output((SessionId, OutputEvent)),
  50    Module((SessionId, ModuleEvent)),
  51    LoadedSource((SessionId, LoadedSourceEvent)),
  52    ClientShutdown(SessionId),
  53    CapabilitiesChanged(SessionId),
  54}
  55
  56actions!(debug_panel, [ToggleFocus]);
  57pub struct DebugPanel {
  58    size: Pixels,
  59    sessions: Vec<Entity<DebugSession>>,
  60    active_session: Option<Entity<DebugSession>>,
  61    /// This represents the last debug definition that was created in the new session modal
  62    pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
  63    project: Entity<Project>,
  64    workspace: WeakEntity<Workspace>,
  65    focus_handle: FocusHandle,
  66    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
  67    fs: Arc<dyn Fs>,
  68}
  69
  70impl DebugPanel {
  71    pub fn new(
  72        workspace: &Workspace,
  73        _window: &mut Window,
  74        cx: &mut Context<Workspace>,
  75    ) -> Entity<Self> {
  76        cx.new(|cx| {
  77            let project = workspace.project().clone();
  78
  79            let debug_panel = Self {
  80                size: px(300.),
  81                sessions: vec![],
  82                active_session: None,
  83                past_debug_definition: None,
  84                focus_handle: cx.focus_handle(),
  85                project,
  86                workspace: workspace.weak_handle(),
  87                context_menu: None,
  88                fs: workspace.app_state().fs.clone(),
  89            };
  90
  91            debug_panel
  92        })
  93    }
  94
  95    fn filter_action_types(&self, cx: &mut App) {
  96        let (has_active_session, supports_restart, support_step_back, status) = self
  97            .active_session()
  98            .map(|item| {
  99                let running = item.read(cx).running_state().clone();
 100                let caps = running.read(cx).capabilities(cx);
 101                (
 102                    !running.read(cx).session().read(cx).is_terminated(),
 103                    caps.supports_restart_request.unwrap_or_default(),
 104                    caps.supports_step_back.unwrap_or_default(),
 105                    running.read(cx).thread_status(cx),
 106                )
 107            })
 108            .unwrap_or((false, false, false, None));
 109
 110        let filter = CommandPaletteFilter::global_mut(cx);
 111        let debugger_action_types = [
 112            TypeId::of::<Detach>(),
 113            TypeId::of::<Stop>(),
 114            TypeId::of::<ToggleIgnoreBreakpoints>(),
 115        ];
 116
 117        let running_action_types = [TypeId::of::<Pause>()];
 118
 119        let stopped_action_type = [
 120            TypeId::of::<Continue>(),
 121            TypeId::of::<StepOver>(),
 122            TypeId::of::<StepInto>(),
 123            TypeId::of::<StepOut>(),
 124            TypeId::of::<editor::actions::DebuggerRunToCursor>(),
 125            TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
 126        ];
 127
 128        let step_back_action_type = [TypeId::of::<StepBack>()];
 129        let restart_action_type = [TypeId::of::<Restart>()];
 130
 131        if has_active_session {
 132            filter.show_action_types(debugger_action_types.iter());
 133
 134            if supports_restart {
 135                filter.show_action_types(restart_action_type.iter());
 136            } else {
 137                filter.hide_action_types(&restart_action_type);
 138            }
 139
 140            if support_step_back {
 141                filter.show_action_types(step_back_action_type.iter());
 142            } else {
 143                filter.hide_action_types(&step_back_action_type);
 144            }
 145
 146            match status {
 147                Some(ThreadStatus::Running) => {
 148                    filter.show_action_types(running_action_types.iter());
 149                    filter.hide_action_types(&stopped_action_type);
 150                }
 151                Some(ThreadStatus::Stopped) => {
 152                    filter.show_action_types(stopped_action_type.iter());
 153                    filter.hide_action_types(&running_action_types);
 154                }
 155                _ => {
 156                    filter.hide_action_types(&running_action_types);
 157                    filter.hide_action_types(&stopped_action_type);
 158                }
 159            }
 160        } else {
 161            // show only the `debug: start`
 162            filter.hide_action_types(&debugger_action_types);
 163            filter.hide_action_types(&step_back_action_type);
 164            filter.hide_action_types(&restart_action_type);
 165            filter.hide_action_types(&running_action_types);
 166            filter.hide_action_types(&stopped_action_type);
 167        }
 168    }
 169
 170    pub fn load(
 171        workspace: WeakEntity<Workspace>,
 172        cx: &mut AsyncWindowContext,
 173    ) -> Task<Result<Entity<Self>>> {
 174        cx.spawn(async move |cx| {
 175            workspace.update_in(cx, |workspace, window, cx| {
 176                let debug_panel = DebugPanel::new(workspace, window, cx);
 177
 178                workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
 179                    workspace.project().read(cx).breakpoint_store().update(
 180                        cx,
 181                        |breakpoint_store, cx| {
 182                            breakpoint_store.clear_breakpoints(cx);
 183                        },
 184                    )
 185                });
 186
 187                cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
 188                    Self::filter_action_types(debug_panel, cx);
 189                })
 190                .detach();
 191
 192                cx.observe(&debug_panel, |_, debug_panel, cx| {
 193                    debug_panel.update(cx, |debug_panel, cx| {
 194                        Self::filter_action_types(debug_panel, cx);
 195                    });
 196                })
 197                .detach();
 198                workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
 199
 200                debug_panel
 201            })
 202        })
 203    }
 204
 205    pub fn start_session(
 206        &mut self,
 207        scenario: DebugScenario,
 208        task_context: TaskContext,
 209        active_buffer: Option<Entity<Buffer>>,
 210        worktree_id: Option<WorktreeId>,
 211        window: &mut Window,
 212        cx: &mut Context<Self>,
 213    ) {
 214        let dap_store = self.project.read(cx).dap_store();
 215        let session = dap_store.update(cx, |dap_store, cx| {
 216            dap_store.new_session(
 217                scenario.label.clone(),
 218                DebugAdapterName(scenario.adapter.clone()),
 219                None,
 220                cx,
 221            )
 222        });
 223        let task = cx.spawn_in(window, {
 224            let session = session.clone();
 225            async move |this, cx| {
 226                let debug_session =
 227                    Self::register_session(this.clone(), session.clone(), cx).await?;
 228                let definition = debug_session
 229                    .update_in(cx, |debug_session, window, cx| {
 230                        debug_session.running_state().update(cx, |running, cx| {
 231                            running.resolve_scenario(
 232                                scenario,
 233                                task_context,
 234                                active_buffer,
 235                                worktree_id,
 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                session
 254                    .update(cx, |session, cx| {
 255                        session
 256                            .console_output(cx)
 257                            .unbounded_send(format!("error: {}", error))
 258                            .ok();
 259                        session.shutdown(cx)
 260                    })?
 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
 611        let new_session_button = || {
 612            IconButton::new("debug-new-session", IconName::Plus)
 613                .icon_size(IconSize::Small)
 614                .on_click({
 615                    move |_, window, cx| window.dispatch_action(crate::Start.boxed_clone(), cx)
 616                })
 617                .tooltip({
 618                    let focus_handle = focus_handle.clone();
 619                    move |window, cx| {
 620                        Tooltip::for_action_in(
 621                            "Start Debug Session",
 622                            &crate::Start,
 623                            &focus_handle,
 624                            window,
 625                            cx,
 626                        )
 627                    }
 628                })
 629        };
 630
 631        Some(
 632            div.border_b_1()
 633                .border_color(cx.theme().colors().border)
 634                .p_1()
 635                .justify_between()
 636                .w_full()
 637                .when(is_side, |this| this.gap_1())
 638                .child(
 639                    h_flex()
 640                        .child(
 641                            h_flex().gap_2().w_full().when_some(
 642                                active_session
 643                                    .as_ref()
 644                                    .map(|session| session.read(cx).running_state()),
 645                                |this, running_session| {
 646                                    let thread_status =
 647                                        running_session.read(cx).thread_status(cx).unwrap_or(
 648                                            project::debugger::session::ThreadStatus::Exited,
 649                                        );
 650                                    let capabilities = running_session.read(cx).capabilities(cx);
 651                                    this.map(|this| {
 652                                        if thread_status == ThreadStatus::Running {
 653                                            this.child(
 654                                                IconButton::new(
 655                                                    "debug-pause",
 656                                                    IconName::DebugPause,
 657                                                )
 658                                                .icon_size(IconSize::XSmall)
 659                                                .shape(ui::IconButtonShape::Square)
 660                                                .on_click(window.listener_for(
 661                                                    &running_session,
 662                                                    |this, _, _window, cx| {
 663                                                        this.pause_thread(cx);
 664                                                    },
 665                                                ))
 666                                                .tooltip({
 667                                                    let focus_handle = focus_handle.clone();
 668                                                    move |window, cx| {
 669                                                        Tooltip::for_action_in(
 670                                                            "Pause program",
 671                                                            &Pause,
 672                                                            &focus_handle,
 673                                                            window,
 674                                                            cx,
 675                                                        )
 676                                                    }
 677                                                }),
 678                                            )
 679                                        } else {
 680                                            this.child(
 681                                                IconButton::new(
 682                                                    "debug-continue",
 683                                                    IconName::DebugContinue,
 684                                                )
 685                                                .icon_size(IconSize::XSmall)
 686                                                .shape(ui::IconButtonShape::Square)
 687                                                .on_click(window.listener_for(
 688                                                    &running_session,
 689                                                    |this, _, _window, cx| this.continue_thread(cx),
 690                                                ))
 691                                                .disabled(thread_status != ThreadStatus::Stopped)
 692                                                .tooltip({
 693                                                    let focus_handle = focus_handle.clone();
 694                                                    move |window, cx| {
 695                                                        Tooltip::for_action_in(
 696                                                            "Continue program",
 697                                                            &Continue,
 698                                                            &focus_handle,
 699                                                            window,
 700                                                            cx,
 701                                                        )
 702                                                    }
 703                                                }),
 704                                            )
 705                                        }
 706                                    })
 707                                    .child(
 708                                        IconButton::new("debug-step-over", IconName::ArrowRight)
 709                                            .icon_size(IconSize::XSmall)
 710                                            .shape(ui::IconButtonShape::Square)
 711                                            .on_click(window.listener_for(
 712                                                &running_session,
 713                                                |this, _, _window, cx| {
 714                                                    this.step_over(cx);
 715                                                },
 716                                            ))
 717                                            .disabled(thread_status != ThreadStatus::Stopped)
 718                                            .tooltip({
 719                                                let focus_handle = focus_handle.clone();
 720                                                move |window, cx| {
 721                                                    Tooltip::for_action_in(
 722                                                        "Step over",
 723                                                        &StepOver,
 724                                                        &focus_handle,
 725                                                        window,
 726                                                        cx,
 727                                                    )
 728                                                }
 729                                            }),
 730                                    )
 731                                    .child(
 732                                        IconButton::new("debug-step-out", IconName::ArrowUpRight)
 733                                            .icon_size(IconSize::XSmall)
 734                                            .shape(ui::IconButtonShape::Square)
 735                                            .on_click(window.listener_for(
 736                                                &running_session,
 737                                                |this, _, _window, cx| {
 738                                                    this.step_out(cx);
 739                                                },
 740                                            ))
 741                                            .disabled(thread_status != ThreadStatus::Stopped)
 742                                            .tooltip({
 743                                                let focus_handle = focus_handle.clone();
 744                                                move |window, cx| {
 745                                                    Tooltip::for_action_in(
 746                                                        "Step out",
 747                                                        &StepOut,
 748                                                        &focus_handle,
 749                                                        window,
 750                                                        cx,
 751                                                    )
 752                                                }
 753                                            }),
 754                                    )
 755                                    .child(
 756                                        IconButton::new(
 757                                            "debug-step-into",
 758                                            IconName::ArrowDownRight,
 759                                        )
 760                                        .icon_size(IconSize::XSmall)
 761                                        .shape(ui::IconButtonShape::Square)
 762                                        .on_click(window.listener_for(
 763                                            &running_session,
 764                                            |this, _, _window, cx| {
 765                                                this.step_in(cx);
 766                                            },
 767                                        ))
 768                                        .disabled(thread_status != ThreadStatus::Stopped)
 769                                        .tooltip({
 770                                            let focus_handle = focus_handle.clone();
 771                                            move |window, cx| {
 772                                                Tooltip::for_action_in(
 773                                                    "Step in",
 774                                                    &StepInto,
 775                                                    &focus_handle,
 776                                                    window,
 777                                                    cx,
 778                                                )
 779                                            }
 780                                        }),
 781                                    )
 782                                    .child(Divider::vertical())
 783                                    .child(
 784                                        IconButton::new(
 785                                            "debug-enable-breakpoint",
 786                                            IconName::DebugDisabledBreakpoint,
 787                                        )
 788                                        .icon_size(IconSize::XSmall)
 789                                        .shape(ui::IconButtonShape::Square)
 790                                        .disabled(thread_status != ThreadStatus::Stopped),
 791                                    )
 792                                    .child(
 793                                        IconButton::new(
 794                                            "debug-disable-breakpoint",
 795                                            IconName::CircleOff,
 796                                        )
 797                                        .icon_size(IconSize::XSmall)
 798                                        .shape(ui::IconButtonShape::Square)
 799                                        .disabled(thread_status != ThreadStatus::Stopped),
 800                                    )
 801                                    .child(
 802                                        IconButton::new(
 803                                            "debug-disable-all-breakpoints",
 804                                            IconName::BugOff,
 805                                        )
 806                                        .icon_size(IconSize::XSmall)
 807                                        .shape(ui::IconButtonShape::Square)
 808                                        .disabled(
 809                                            thread_status == ThreadStatus::Exited
 810                                                || thread_status == ThreadStatus::Ended,
 811                                        )
 812                                        .on_click(window.listener_for(
 813                                            &running_session,
 814                                            |this, _, _window, cx| {
 815                                                this.toggle_ignore_breakpoints(cx);
 816                                            },
 817                                        ))
 818                                        .tooltip({
 819                                            let focus_handle = focus_handle.clone();
 820                                            move |window, cx| {
 821                                                Tooltip::for_action_in(
 822                                                    "Disable all breakpoints",
 823                                                    &ToggleIgnoreBreakpoints,
 824                                                    &focus_handle,
 825                                                    window,
 826                                                    cx,
 827                                                )
 828                                            }
 829                                        }),
 830                                    )
 831                                    .child(Divider::vertical())
 832                                    .child(
 833                                        IconButton::new("debug-restart", IconName::DebugRestart)
 834                                            .icon_size(IconSize::XSmall)
 835                                            .on_click(window.listener_for(
 836                                                &running_session,
 837                                                |this, _, _window, cx| {
 838                                                    this.restart_session(cx);
 839                                                },
 840                                            ))
 841                                            .tooltip({
 842                                                let focus_handle = focus_handle.clone();
 843                                                move |window, cx| {
 844                                                    Tooltip::for_action_in(
 845                                                        "Restart",
 846                                                        &Restart,
 847                                                        &focus_handle,
 848                                                        window,
 849                                                        cx,
 850                                                    )
 851                                                }
 852                                            }),
 853                                    )
 854                                    .child(
 855                                        IconButton::new("debug-stop", IconName::Power)
 856                                            .icon_size(IconSize::XSmall)
 857                                            .on_click(window.listener_for(
 858                                                &running_session,
 859                                                |this, _, _window, cx| {
 860                                                    this.stop_thread(cx);
 861                                                },
 862                                            ))
 863                                            .disabled(
 864                                                thread_status != ThreadStatus::Stopped
 865                                                    && thread_status != ThreadStatus::Running,
 866                                            )
 867                                            .tooltip({
 868                                                let focus_handle = focus_handle.clone();
 869                                                let label = if capabilities
 870                                                    .supports_terminate_threads_request
 871                                                    .unwrap_or_default()
 872                                                {
 873                                                    "Terminate Thread"
 874                                                } else {
 875                                                    "Terminate All Threads"
 876                                                };
 877                                                move |window, cx| {
 878                                                    Tooltip::for_action_in(
 879                                                        label,
 880                                                        &Stop,
 881                                                        &focus_handle,
 882                                                        window,
 883                                                        cx,
 884                                                    )
 885                                                }
 886                                            }),
 887                                    )
 888                                    .child(
 889                                        IconButton::new("debug-disconnect", IconName::DebugDetach)
 890                                            .icon_size(IconSize::XSmall)
 891                                            .on_click(window.listener_for(
 892                                                &running_session,
 893                                                |this, _, _, cx| {
 894                                                    this.detach_client(cx);
 895                                                },
 896                                            ))
 897                                            .tooltip({
 898                                                let focus_handle = focus_handle.clone();
 899                                                move |window, cx| {
 900                                                    Tooltip::for_action_in(
 901                                                        "Detach",
 902                                                        &Detach,
 903                                                        &focus_handle,
 904                                                        window,
 905                                                        cx,
 906                                                    )
 907                                                }
 908                                            }),
 909                                    )
 910                                },
 911                            ),
 912                        )
 913                        .justify_around()
 914                        .when(is_side, |this| this.child(new_session_button())),
 915                )
 916                .child(
 917                    h_flex()
 918                        .gap_2()
 919                        .when(is_side, |this| this.justify_between())
 920                        .child(
 921                            h_flex().when_some(
 922                                active_session
 923                                    .as_ref()
 924                                    .map(|session| session.read(cx).running_state())
 925                                    .cloned(),
 926                                |this, session| {
 927                                    this.child(
 928                                        session.update(cx, |this, cx| {
 929                                            this.thread_dropdown(window, cx)
 930                                        }),
 931                                    )
 932                                    .when(!is_side, |this| this.gap_2().child(Divider::vertical()))
 933                                },
 934                            ),
 935                        )
 936                        .child(
 937                            h_flex()
 938                                .when_some(active_session.as_ref(), |this, session| {
 939                                    let context_menu =
 940                                        self.sessions_drop_down_menu(session, window, cx);
 941                                    this.child(context_menu).gap_2().child(Divider::vertical())
 942                                })
 943                                .when(!is_side, |this| this.child(new_session_button())),
 944                        ),
 945                ),
 946        )
 947    }
 948
 949    fn activate_pane_in_direction(
 950        &mut self,
 951        direction: SplitDirection,
 952        window: &mut Window,
 953        cx: &mut Context<Self>,
 954    ) {
 955        if let Some(session) = self.active_session() {
 956            session.update(cx, |session, cx| {
 957                session.running_state().update(cx, |running, cx| {
 958                    running.activate_pane_in_direction(direction, window, cx);
 959                })
 960            });
 961        }
 962    }
 963
 964    fn activate_item(
 965        &mut self,
 966        item: DebuggerPaneItem,
 967        window: &mut Window,
 968        cx: &mut Context<Self>,
 969    ) {
 970        if let Some(session) = self.active_session() {
 971            session.update(cx, |session, cx| {
 972                session.running_state().update(cx, |running, cx| {
 973                    running.activate_item(item, window, cx);
 974                });
 975            });
 976        }
 977    }
 978
 979    fn activate_session(
 980        &mut self,
 981        session_item: Entity<DebugSession>,
 982        window: &mut Window,
 983        cx: &mut Context<Self>,
 984    ) {
 985        debug_assert!(self.sessions.contains(&session_item));
 986        session_item.focus_handle(cx).focus(window);
 987        session_item.update(cx, |this, cx| {
 988            this.running_state().update(cx, |this, cx| {
 989                this.go_to_selected_stack_frame(window, cx);
 990            });
 991        });
 992        self.active_session = Some(session_item);
 993        cx.notify();
 994    }
 995}
 996
 997impl EventEmitter<PanelEvent> for DebugPanel {}
 998impl EventEmitter<DebugPanelEvent> for DebugPanel {}
 999
1000impl Focusable for DebugPanel {
1001    fn focus_handle(&self, _: &App) -> FocusHandle {
1002        self.focus_handle.clone()
1003    }
1004}
1005
1006impl Panel for DebugPanel {
1007    fn persistent_name() -> &'static str {
1008        "DebugPanel"
1009    }
1010
1011    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1012        match DebuggerSettings::get_global(cx).dock {
1013            DebugPanelDockPosition::Left => DockPosition::Left,
1014            DebugPanelDockPosition::Bottom => DockPosition::Bottom,
1015            DebugPanelDockPosition::Right => DockPosition::Right,
1016        }
1017    }
1018
1019    fn position_is_valid(&self, _: DockPosition) -> bool {
1020        true
1021    }
1022
1023    fn set_position(
1024        &mut self,
1025        position: DockPosition,
1026        window: &mut Window,
1027        cx: &mut Context<Self>,
1028    ) {
1029        if position.axis() != self.position(window, cx).axis() {
1030            self.sessions.iter().for_each(|session_item| {
1031                session_item.update(cx, |item, cx| {
1032                    item.running_state()
1033                        .update(cx, |state, _| state.invert_axies())
1034                })
1035            });
1036        }
1037
1038        settings::update_settings_file::<DebuggerSettings>(
1039            self.fs.clone(),
1040            cx,
1041            move |settings, _| {
1042                let dock = match position {
1043                    DockPosition::Left => DebugPanelDockPosition::Left,
1044                    DockPosition::Bottom => DebugPanelDockPosition::Bottom,
1045                    DockPosition::Right => DebugPanelDockPosition::Right,
1046                };
1047                settings.dock = dock;
1048            },
1049        );
1050    }
1051
1052    fn size(&self, _window: &Window, _: &App) -> Pixels {
1053        self.size
1054    }
1055
1056    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
1057        self.size = size.unwrap();
1058    }
1059
1060    fn remote_id() -> Option<proto::PanelId> {
1061        Some(proto::PanelId::DebugPanel)
1062    }
1063
1064    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
1065        Some(IconName::Debug)
1066    }
1067
1068    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1069        if DebuggerSettings::get_global(cx).button {
1070            Some("Debug Panel")
1071        } else {
1072            None
1073        }
1074    }
1075
1076    fn toggle_action(&self) -> Box<dyn Action> {
1077        Box::new(ToggleFocus)
1078    }
1079
1080    fn pane(&self) -> Option<Entity<Pane>> {
1081        None
1082    }
1083
1084    fn activation_priority(&self) -> u32 {
1085        9
1086    }
1087
1088    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1089}
1090
1091impl Render for DebugPanel {
1092    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1093        let has_sessions = self.sessions.len() > 0;
1094        let this = cx.weak_entity();
1095        debug_assert_eq!(has_sessions, self.active_session.is_some());
1096
1097        if self
1098            .active_session
1099            .as_ref()
1100            .map(|session| session.read(cx).running_state())
1101            .map(|state| state.read(cx).has_open_context_menu(cx))
1102            .unwrap_or(false)
1103        {
1104            self.context_menu.take();
1105        }
1106
1107        v_flex()
1108            .size_full()
1109            .key_context("DebugPanel")
1110            .child(h_flex().children(self.top_controls_strip(window, cx)))
1111            .track_focus(&self.focus_handle(cx))
1112            .on_action({
1113                let this = this.clone();
1114                move |_: &workspace::ActivatePaneLeft, window, cx| {
1115                    this.update(cx, |this, cx| {
1116                        this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1117                    })
1118                    .ok();
1119                }
1120            })
1121            .on_action({
1122                let this = this.clone();
1123                move |_: &workspace::ActivatePaneRight, window, cx| {
1124                    this.update(cx, |this, cx| {
1125                        this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1126                    })
1127                    .ok();
1128                }
1129            })
1130            .on_action({
1131                let this = this.clone();
1132                move |_: &workspace::ActivatePaneUp, window, cx| {
1133                    this.update(cx, |this, cx| {
1134                        this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1135                    })
1136                    .ok();
1137                }
1138            })
1139            .on_action({
1140                let this = this.clone();
1141                move |_: &workspace::ActivatePaneDown, window, cx| {
1142                    this.update(cx, |this, cx| {
1143                        this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1144                    })
1145                    .ok();
1146                }
1147            })
1148            .on_action({
1149                let this = this.clone();
1150                move |_: &FocusConsole, window, cx| {
1151                    this.update(cx, |this, cx| {
1152                        this.activate_item(DebuggerPaneItem::Console, window, cx);
1153                    })
1154                    .ok();
1155                }
1156            })
1157            .on_action({
1158                let this = this.clone();
1159                move |_: &FocusVariables, window, cx| {
1160                    this.update(cx, |this, cx| {
1161                        this.activate_item(DebuggerPaneItem::Variables, window, cx);
1162                    })
1163                    .ok();
1164                }
1165            })
1166            .on_action({
1167                let this = this.clone();
1168                move |_: &FocusBreakpointList, window, cx| {
1169                    this.update(cx, |this, cx| {
1170                        this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1171                    })
1172                    .ok();
1173                }
1174            })
1175            .on_action({
1176                let this = this.clone();
1177                move |_: &FocusFrames, window, cx| {
1178                    this.update(cx, |this, cx| {
1179                        this.activate_item(DebuggerPaneItem::Frames, window, cx);
1180                    })
1181                    .ok();
1182                }
1183            })
1184            .on_action({
1185                let this = this.clone();
1186                move |_: &FocusModules, window, cx| {
1187                    this.update(cx, |this, cx| {
1188                        this.activate_item(DebuggerPaneItem::Modules, window, cx);
1189                    })
1190                    .ok();
1191                }
1192            })
1193            .on_action({
1194                let this = this.clone();
1195                move |_: &FocusLoadedSources, window, cx| {
1196                    this.update(cx, |this, cx| {
1197                        this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1198                    })
1199                    .ok();
1200                }
1201            })
1202            .on_action({
1203                let this = this.clone();
1204                move |_: &FocusTerminal, window, cx| {
1205                    this.update(cx, |this, cx| {
1206                        this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1207                    })
1208                    .ok();
1209                }
1210            })
1211            .when(self.active_session.is_some(), |this| {
1212                this.on_mouse_down(
1213                    MouseButton::Right,
1214                    cx.listener(|this, event: &MouseDownEvent, window, cx| {
1215                        if this
1216                            .active_session
1217                            .as_ref()
1218                            .map(|session| {
1219                                let state = session.read(cx).running_state();
1220                                state.read(cx).has_pane_at_position(event.position)
1221                            })
1222                            .unwrap_or(false)
1223                        {
1224                            this.deploy_context_menu(event.position, window, cx);
1225                        }
1226                    }),
1227                )
1228                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1229                    deferred(
1230                        anchored()
1231                            .position(*position)
1232                            .anchor(gpui::Corner::TopLeft)
1233                            .child(menu.clone()),
1234                    )
1235                    .with_priority(1)
1236                }))
1237            })
1238            .map(|this| {
1239                if has_sessions {
1240                    this.children(self.active_session.clone())
1241                } else {
1242                    this.child(
1243                        v_flex()
1244                            .h_full()
1245                            .gap_1()
1246                            .items_center()
1247                            .justify_center()
1248                            .child(
1249                                h_flex().child(
1250                                    Label::new("No Debugging Sessions")
1251                                        .size(LabelSize::Small)
1252                                        .color(Color::Muted),
1253                                ),
1254                            )
1255                            .child(
1256                                h_flex().flex_shrink().child(
1257                                    Button::new("spawn-new-session-empty-state", "New Session")
1258                                        .size(ButtonSize::Large)
1259                                        .on_click(|_, window, cx| {
1260                                            window.dispatch_action(crate::Start.boxed_clone(), cx);
1261                                        }),
1262                                ),
1263                            ),
1264                    )
1265                }
1266            })
1267            .into_any()
1268    }
1269}
1270
1271struct DebuggerProvider(Entity<DebugPanel>);
1272
1273impl workspace::DebuggerProvider for DebuggerProvider {
1274    fn start_session(
1275        &self,
1276        definition: DebugScenario,
1277        context: TaskContext,
1278        buffer: Option<Entity<Buffer>>,
1279        window: &mut Window,
1280        cx: &mut App,
1281    ) {
1282        self.0.update(cx, |_, cx| {
1283            cx.defer_in(window, |this, window, cx| {
1284                this.start_session(definition, context, buffer, None, window, cx);
1285            })
1286        })
1287    }
1288}