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