debugger_panel.rs

   1use crate::persistence::DebuggerPaneItem;
   2use crate::{
   3    ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, FocusBreakpointList,
   4    FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,
   5    Pause, Restart, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
   6    persistence,
   7};
   8use crate::{new_session_modal::NewSessionModal, session::DebugSession};
   9use anyhow::Result;
  10use command_palette_hooks::CommandPaletteFilter;
  11use dap::adapters::DebugAdapterName;
  12use dap::debugger_settings::DebugPanelDockPosition;
  13use dap::{
  14    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
  15    client::SessionId, debugger_settings::DebuggerSettings,
  16};
  17use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
  18use gpui::{
  19    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
  20    FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
  21    actions, anchored, deferred,
  22};
  23
  24use language::Buffer;
  25use project::debugger::session::{Session, SessionStateEvent};
  26use project::{Fs, WorktreeId};
  27use project::{Project, debugger::session::ThreadStatus};
  28use rpc::proto::{self};
  29use settings::Settings;
  30use std::any::TypeId;
  31use std::sync::Arc;
  32use task::{DebugScenario, TaskContext};
  33use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
  34use workspace::SplitDirection;
  35use workspace::{
  36    Pane, Workspace,
  37    dock::{DockPosition, Panel, PanelEvent},
  38};
  39
  40pub enum DebugPanelEvent {
  41    Exited(SessionId),
  42    Terminated(SessionId),
  43    Stopped {
  44        client_id: SessionId,
  45        event: StoppedEvent,
  46        go_to_stack_frame: bool,
  47    },
  48    Thread((SessionId, ThreadEvent)),
  49    Continued((SessionId, ContinuedEvent)),
  50    Output((SessionId, OutputEvent)),
  51    Module((SessionId, ModuleEvent)),
  52    LoadedSource((SessionId, LoadedSourceEvent)),
  53    ClientShutdown(SessionId),
  54    CapabilitiesChanged(SessionId),
  55}
  56
  57actions!(debug_panel, [ToggleFocus]);
  58pub struct DebugPanel {
  59    size: Pixels,
  60    sessions: Vec<Entity<DebugSession>>,
  61    active_session: Option<Entity<DebugSession>>,
  62    /// This represents the last debug definition that was created in the new session modal
  63    pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
  64    project: Entity<Project>,
  65    workspace: WeakEntity<Workspace>,
  66    focus_handle: FocusHandle,
  67    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
  68    fs: Arc<dyn Fs>,
  69}
  70
  71impl DebugPanel {
  72    pub fn new(
  73        workspace: &Workspace,
  74        _window: &mut Window,
  75        cx: &mut Context<Workspace>,
  76    ) -> Entity<Self> {
  77        cx.new(|cx| {
  78            let project = workspace.project().clone();
  79
  80            let debug_panel = Self {
  81                size: px(300.),
  82                sessions: vec![],
  83                active_session: None,
  84                past_debug_definition: None,
  85                focus_handle: cx.focus_handle(),
  86                project,
  87                workspace: workspace.weak_handle(),
  88                context_menu: None,
  89                fs: workspace.app_state().fs.clone(),
  90            };
  91
  92            debug_panel
  93        })
  94    }
  95
  96    fn filter_action_types(&self, cx: &mut App) {
  97        let (has_active_session, supports_restart, support_step_back, status) = self
  98            .active_session()
  99            .map(|item| {
 100                let running = item.read(cx).running_state().clone();
 101                let caps = running.read(cx).capabilities(cx);
 102                (
 103                    !running.read(cx).session().read(cx).is_terminated(),
 104                    caps.supports_restart_request.unwrap_or_default(),
 105                    caps.supports_step_back.unwrap_or_default(),
 106                    running.read(cx).thread_status(cx),
 107                )
 108            })
 109            .unwrap_or((false, false, false, None));
 110
 111        let filter = CommandPaletteFilter::global_mut(cx);
 112        let debugger_action_types = [
 113            TypeId::of::<Disconnect>(),
 114            TypeId::of::<Stop>(),
 115            TypeId::of::<ToggleIgnoreBreakpoints>(),
 116        ];
 117
 118        let running_action_types = [TypeId::of::<Pause>()];
 119
 120        let stopped_action_type = [
 121            TypeId::of::<Continue>(),
 122            TypeId::of::<StepOver>(),
 123            TypeId::of::<StepInto>(),
 124            TypeId::of::<StepOut>(),
 125            TypeId::of::<editor::actions::DebuggerRunToCursor>(),
 126            TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
 127        ];
 128
 129        let step_back_action_type = [TypeId::of::<StepBack>()];
 130        let restart_action_type = [TypeId::of::<Restart>()];
 131
 132        if has_active_session {
 133            filter.show_action_types(debugger_action_types.iter());
 134
 135            if supports_restart {
 136                filter.show_action_types(restart_action_type.iter());
 137            } else {
 138                filter.hide_action_types(&restart_action_type);
 139            }
 140
 141            if support_step_back {
 142                filter.show_action_types(step_back_action_type.iter());
 143            } else {
 144                filter.hide_action_types(&step_back_action_type);
 145            }
 146
 147            match status {
 148                Some(ThreadStatus::Running) => {
 149                    filter.show_action_types(running_action_types.iter());
 150                    filter.hide_action_types(&stopped_action_type);
 151                }
 152                Some(ThreadStatus::Stopped) => {
 153                    filter.show_action_types(stopped_action_type.iter());
 154                    filter.hide_action_types(&running_action_types);
 155                }
 156                _ => {
 157                    filter.hide_action_types(&running_action_types);
 158                    filter.hide_action_types(&stopped_action_type);
 159                }
 160            }
 161        } else {
 162            // show only the `debug: start`
 163            filter.hide_action_types(&debugger_action_types);
 164            filter.hide_action_types(&step_back_action_type);
 165            filter.hide_action_types(&restart_action_type);
 166            filter.hide_action_types(&running_action_types);
 167            filter.hide_action_types(&stopped_action_type);
 168        }
 169    }
 170
 171    pub fn load(
 172        workspace: WeakEntity<Workspace>,
 173        cx: &mut AsyncWindowContext,
 174    ) -> Task<Result<Entity<Self>>> {
 175        cx.spawn(async move |cx| {
 176            workspace.update_in(cx, |workspace, window, cx| {
 177                let debug_panel = DebugPanel::new(workspace, window, cx);
 178
 179                workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
 180                    workspace.project().read(cx).breakpoint_store().update(
 181                        cx,
 182                        |breakpoint_store, cx| {
 183                            breakpoint_store.clear_breakpoints(cx);
 184                        },
 185                    )
 186                });
 187
 188                cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
 189                    Self::filter_action_types(debug_panel, cx);
 190                })
 191                .detach();
 192
 193                cx.observe(&debug_panel, |_, debug_panel, cx| {
 194                    debug_panel.update(cx, |debug_panel, cx| {
 195                        Self::filter_action_types(debug_panel, cx);
 196                    });
 197                })
 198                .detach();
 199                workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
 200
 201                debug_panel
 202            })
 203        })
 204    }
 205
 206    pub fn start_session(
 207        &mut self,
 208        scenario: DebugScenario,
 209        task_context: TaskContext,
 210        active_buffer: Option<Entity<Buffer>>,
 211        worktree_id: Option<WorktreeId>,
 212        window: &mut Window,
 213        cx: &mut Context<Self>,
 214    ) {
 215        let dap_store = self.project.read(cx).dap_store();
 216        let workspace = self.workspace.clone();
 217        let session = dap_store.update(cx, |dap_store, cx| {
 218            dap_store.new_session(
 219                scenario.label.clone(),
 220                DebugAdapterName(scenario.adapter.clone()),
 221                None,
 222                cx,
 223            )
 224        });
 225        let task = cx.spawn_in(window, {
 226            let session = session.clone();
 227            async move |this, cx| {
 228                let debug_session =
 229                    Self::register_session(this.clone(), session.clone(), cx).await?;
 230                let definition = debug_session
 231                    .update_in(cx, |debug_session, window, cx| {
 232                        debug_session.running_state().update(cx, |running, cx| {
 233                            running.resolve_scenario(
 234                                scenario,
 235                                task_context,
 236                                active_buffer,
 237                                worktree_id,
 238                                window,
 239                                cx,
 240                            )
 241                        })
 242                    })?
 243                    .await?;
 244
 245                dap_store
 246                    .update(cx, |dap_store, cx| {
 247                        dap_store.boot_session(session.clone(), definition, cx)
 248                    })?
 249                    .await
 250            }
 251        });
 252
 253        cx.spawn(async move |_, cx| {
 254            if let Err(error) = task.await {
 255                log::error!("{:?}", error);
 256                workspace
 257                    .update(cx, |workspace, cx| {
 258                        workspace.show_error(&error, cx);
 259                    })
 260                    .ok();
 261                session
 262                    .update(cx, |session, cx| session.shutdown(cx))?
 263                    .await;
 264            }
 265            anyhow::Ok(())
 266        })
 267        .detach_and_log_err(cx);
 268    }
 269
 270    async fn register_session(
 271        this: WeakEntity<Self>,
 272        session: Entity<Session>,
 273        cx: &mut AsyncWindowContext,
 274    ) -> Result<Entity<DebugSession>> {
 275        let adapter_name = session.update(cx, |session, _| session.adapter())?;
 276        this.update_in(cx, |_, window, cx| {
 277            cx.subscribe_in(
 278                &session,
 279                window,
 280                move |this, session, event: &SessionStateEvent, window, cx| match event {
 281                    SessionStateEvent::Restart => {
 282                        this.handle_restart_request(session.clone(), window, cx);
 283                    }
 284                    SessionStateEvent::SpawnChildSession { request } => {
 285                        this.handle_start_debugging_request(request, session.clone(), window, cx);
 286                    }
 287                    _ => {}
 288                },
 289            )
 290            .detach();
 291        })
 292        .ok();
 293
 294        let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
 295
 296        let (debug_session, workspace) = this.update_in(cx, |this, window, cx| {
 297            this.sessions.retain(|session| {
 298                session
 299                    .read(cx)
 300                    .running_state()
 301                    .read(cx)
 302                    .session()
 303                    .read(cx)
 304                    .is_terminated()
 305            });
 306
 307            let debug_session = DebugSession::running(
 308                this.project.clone(),
 309                this.workspace.clone(),
 310                session,
 311                cx.weak_entity(),
 312                serialized_layout,
 313                this.position(window, cx).axis(),
 314                window,
 315                cx,
 316            );
 317
 318            // We might want to make this an event subscription and only notify when a new thread is selected
 319            // This is used to filter the command menu correctly
 320            cx.observe(
 321                &debug_session.read(cx).running_state().clone(),
 322                |_, _, cx| cx.notify(),
 323            )
 324            .detach();
 325
 326            this.sessions.push(debug_session.clone());
 327            this.activate_session(debug_session.clone(), window, cx);
 328
 329            (debug_session, this.workspace.clone())
 330        })?;
 331
 332        workspace.update_in(cx, |workspace, window, cx| {
 333            workspace.focus_panel::<Self>(window, cx);
 334        })?;
 335
 336        Ok(debug_session)
 337    }
 338
 339    fn handle_restart_request(
 340        &mut self,
 341        mut curr_session: Entity<Session>,
 342        window: &mut Window,
 343        cx: &mut Context<Self>,
 344    ) {
 345        while let Some(parent_session) =
 346            curr_session.read_with(cx, |session, _| session.parent_session().cloned())
 347        {
 348            curr_session = parent_session;
 349        }
 350
 351        let Some(worktree) = curr_session.read(cx).worktree() else {
 352            log::error!("Attempted to start a child session from non local debug session");
 353            return;
 354        };
 355
 356        let dap_store_handle = self.project.read(cx).dap_store().clone();
 357        let label = curr_session.read(cx).label().clone();
 358        let adapter = curr_session.read(cx).adapter().clone();
 359        let binary = curr_session.read(cx).binary().clone();
 360        let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
 361
 362        cx.spawn_in(window, async move |this, cx| {
 363            task.await;
 364
 365            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 366                let session = dap_store.new_session(label, adapter, None, cx);
 367
 368                let task = session.update(cx, |session, cx| {
 369                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 370                });
 371                (session, task)
 372            })?;
 373            Self::register_session(this, session, cx).await?;
 374            task.await
 375        })
 376        .detach_and_log_err(cx);
 377    }
 378
 379    pub fn handle_start_debugging_request(
 380        &mut self,
 381        request: &StartDebuggingRequestArguments,
 382        parent_session: Entity<Session>,
 383        window: &mut Window,
 384        cx: &mut Context<Self>,
 385    ) {
 386        let Some(worktree) = parent_session.read(cx).worktree() else {
 387            log::error!("Attempted to start a child session from non local debug session");
 388            return;
 389        };
 390
 391        let dap_store_handle = self.project.read(cx).dap_store().clone();
 392        let label = parent_session.read(cx).label().clone();
 393        let adapter = parent_session.read(cx).adapter().clone();
 394        let mut binary = parent_session.read(cx).binary().clone();
 395        binary.request_args = request.clone();
 396
 397        cx.spawn_in(window, async move |this, cx| {
 398            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 399                let session =
 400                    dap_store.new_session(label, adapter, Some(parent_session.clone()), cx);
 401
 402                let task = session.update(cx, |session, cx| {
 403                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 404                });
 405                (session, task)
 406            })?;
 407            Self::register_session(this, session, cx).await?;
 408            task.await
 409        })
 410        .detach_and_log_err(cx);
 411    }
 412
 413    pub fn active_session(&self) -> Option<Entity<DebugSession>> {
 414        self.active_session.clone()
 415    }
 416    fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
 417        let Some(session) = self
 418            .sessions
 419            .iter()
 420            .find(|other| entity_id == other.entity_id())
 421            .cloned()
 422        else {
 423            return;
 424        };
 425        session.update(cx, |this, cx| {
 426            this.running_state().update(cx, |this, cx| {
 427                this.serialize_layout(window, cx);
 428            });
 429        });
 430        let session_id = session.update(cx, |this, cx| this.session_id(cx));
 431        let should_prompt = self
 432            .project
 433            .update(cx, |this, cx| {
 434                let session = this.dap_store().read(cx).session_by_id(session_id);
 435                session.map(|session| !session.read(cx).is_terminated())
 436            })
 437            .unwrap_or_default();
 438
 439        cx.spawn_in(window, async move |this, cx| {
 440            if should_prompt {
 441                let response = cx.prompt(
 442                    gpui::PromptLevel::Warning,
 443                    "This Debug Session is still running. Are you sure you want to terminate it?",
 444                    None,
 445                    &["Yes", "No"],
 446                );
 447                if response.await == Ok(1) {
 448                    return;
 449                }
 450            }
 451            session.update(cx, |session, cx| session.shutdown(cx)).ok();
 452            this.update(cx, |this, cx| {
 453                this.sessions.retain(|other| entity_id != other.entity_id());
 454
 455                if let Some(active_session_id) = this
 456                    .active_session
 457                    .as_ref()
 458                    .map(|session| session.entity_id())
 459                {
 460                    if active_session_id == entity_id {
 461                        this.active_session = this.sessions.first().cloned();
 462                    }
 463                }
 464                cx.notify()
 465            })
 466            .ok();
 467        })
 468        .detach();
 469    }
 470    fn sessions_drop_down_menu(
 471        &self,
 472        active_session: &Entity<DebugSession>,
 473        window: &mut Window,
 474        cx: &mut Context<Self>,
 475    ) -> DropdownMenu {
 476        let sessions = self.sessions.clone();
 477        let weak = cx.weak_entity();
 478        let label = active_session.read(cx).label_element(cx);
 479
 480        DropdownMenu::new_with_element(
 481            "debugger-session-list",
 482            label,
 483            ContextMenu::build(window, cx, move |mut this, _, cx| {
 484                let context_menu = cx.weak_entity();
 485                for session in sessions.into_iter() {
 486                    let weak_session = session.downgrade();
 487                    let weak_session_id = weak_session.entity_id();
 488
 489                    this = this.custom_entry(
 490                        {
 491                            let weak = weak.clone();
 492                            let context_menu = context_menu.clone();
 493                            move |_, cx| {
 494                                weak_session
 495                                    .read_with(cx, |session, cx| {
 496                                        let context_menu = context_menu.clone();
 497                                        let id: SharedString =
 498                                            format!("debug-session-{}", session.session_id(cx).0)
 499                                                .into();
 500                                        h_flex()
 501                                            .w_full()
 502                                            .group(id.clone())
 503                                            .justify_between()
 504                                            .child(session.label_element(cx))
 505                                            .child(
 506                                                IconButton::new(
 507                                                    "close-debug-session",
 508                                                    IconName::Close,
 509                                                )
 510                                                .visible_on_hover(id.clone())
 511                                                .icon_size(IconSize::Small)
 512                                                .on_click({
 513                                                    let weak = weak.clone();
 514                                                    move |_, window, cx| {
 515                                                        weak.update(cx, |panel, cx| {
 516                                                            panel.close_session(
 517                                                                weak_session_id,
 518                                                                window,
 519                                                                cx,
 520                                                            );
 521                                                        })
 522                                                        .ok();
 523                                                        context_menu
 524                                                            .update(cx, |this, cx| {
 525                                                                this.cancel(
 526                                                                    &Default::default(),
 527                                                                    window,
 528                                                                    cx,
 529                                                                );
 530                                                            })
 531                                                            .ok();
 532                                                    }
 533                                                }),
 534                                            )
 535                                            .into_any_element()
 536                                    })
 537                                    .unwrap_or_else(|_| div().into_any_element())
 538                            }
 539                        },
 540                        {
 541                            let weak = weak.clone();
 542                            move |window, cx| {
 543                                weak.update(cx, |panel, cx| {
 544                                    panel.activate_session(session.clone(), window, cx);
 545                                })
 546                                .ok();
 547                            }
 548                        },
 549                    );
 550                }
 551                this
 552            }),
 553        )
 554    }
 555
 556    fn deploy_context_menu(
 557        &mut self,
 558        position: Point<Pixels>,
 559        window: &mut Window,
 560        cx: &mut Context<Self>,
 561    ) {
 562        if let Some(running_state) = self
 563            .active_session
 564            .as_ref()
 565            .map(|session| session.read(cx).running_state().clone())
 566        {
 567            let pane_items_status = running_state.read(cx).pane_items_status(cx);
 568            let this = cx.weak_entity();
 569
 570            let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
 571                for (item_kind, is_visible) in pane_items_status.into_iter() {
 572                    menu = menu.toggleable_entry(item_kind, is_visible, IconPosition::End, None, {
 573                        let this = this.clone();
 574                        move |window, cx| {
 575                            this.update(cx, |this, cx| {
 576                                if let Some(running_state) = this
 577                                    .active_session
 578                                    .as_ref()
 579                                    .map(|session| session.read(cx).running_state().clone())
 580                                {
 581                                    running_state.update(cx, |state, cx| {
 582                                        if is_visible {
 583                                            state.remove_pane_item(item_kind, window, cx);
 584                                        } else {
 585                                            state.add_pane_item(item_kind, position, window, cx);
 586                                        }
 587                                    })
 588                                }
 589                            })
 590                            .ok();
 591                        }
 592                    });
 593                }
 594
 595                menu
 596            });
 597
 598            window.focus(&context_menu.focus_handle(cx));
 599            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
 600                this.context_menu.take();
 601                cx.notify();
 602            });
 603            self.context_menu = Some((context_menu, position, subscription));
 604        }
 605    }
 606
 607    fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
 608        let active_session = self.active_session.clone();
 609        let focus_handle = self.focus_handle.clone();
 610        let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
 611        let div = if is_side { v_flex() } else { h_flex() };
 612        let weak_panel = cx.weak_entity();
 613
 614        let new_session_button = || {
 615            IconButton::new("debug-new-session", IconName::Plus)
 616                .icon_size(IconSize::Small)
 617                .on_click({
 618                    let workspace = self.workspace.clone();
 619                    let weak_panel = weak_panel.clone();
 620                    let past_debug_definition = self.past_debug_definition.clone();
 621                    move |_, window, cx| {
 622                        let weak_panel = weak_panel.clone();
 623                        let past_debug_definition = past_debug_definition.clone();
 624
 625                        let _ = workspace.update(cx, |this, cx| {
 626                            let workspace = cx.weak_entity();
 627                            this.toggle_modal(window, cx, |window, cx| {
 628                                NewSessionModal::new(
 629                                    past_debug_definition,
 630                                    weak_panel,
 631                                    workspace,
 632                                    None,
 633                                    window,
 634                                    cx,
 635                                )
 636                            });
 637                        });
 638                    }
 639                })
 640                .tooltip({
 641                    let focus_handle = focus_handle.clone();
 642                    move |window, cx| {
 643                        Tooltip::for_action_in(
 644                            "New Debug Session",
 645                            &CreateDebuggingSession,
 646                            &focus_handle,
 647                            window,
 648                            cx,
 649                        )
 650                    }
 651                })
 652        };
 653
 654        Some(
 655            div.border_b_1()
 656                .border_color(cx.theme().colors().border)
 657                .p_1()
 658                .justify_between()
 659                .w_full()
 660                .when(is_side, |this| this.gap_1())
 661                .child(
 662                    h_flex()
 663                        .child(
 664                            h_flex().gap_2().w_full().when_some(
 665                                active_session
 666                                    .as_ref()
 667                                    .map(|session| session.read(cx).running_state()),
 668                                |this, running_session| {
 669                                    let thread_status =
 670                                        running_session.read(cx).thread_status(cx).unwrap_or(
 671                                            project::debugger::session::ThreadStatus::Exited,
 672                                        );
 673                                    let capabilities = running_session.read(cx).capabilities(cx);
 674                                    this.map(|this| {
 675                                        if thread_status == ThreadStatus::Running {
 676                                            this.child(
 677                                                IconButton::new(
 678                                                    "debug-pause",
 679                                                    IconName::DebugPause,
 680                                                )
 681                                                .icon_size(IconSize::XSmall)
 682                                                .shape(ui::IconButtonShape::Square)
 683                                                .on_click(window.listener_for(
 684                                                    &running_session,
 685                                                    |this, _, _window, cx| {
 686                                                        this.pause_thread(cx);
 687                                                    },
 688                                                ))
 689                                                .tooltip({
 690                                                    let focus_handle = focus_handle.clone();
 691                                                    move |window, cx| {
 692                                                        Tooltip::for_action_in(
 693                                                            "Pause program",
 694                                                            &Pause,
 695                                                            &focus_handle,
 696                                                            window,
 697                                                            cx,
 698                                                        )
 699                                                    }
 700                                                }),
 701                                            )
 702                                        } else {
 703                                            this.child(
 704                                                IconButton::new(
 705                                                    "debug-continue",
 706                                                    IconName::DebugContinue,
 707                                                )
 708                                                .icon_size(IconSize::XSmall)
 709                                                .shape(ui::IconButtonShape::Square)
 710                                                .on_click(window.listener_for(
 711                                                    &running_session,
 712                                                    |this, _, _window, cx| this.continue_thread(cx),
 713                                                ))
 714                                                .disabled(thread_status != ThreadStatus::Stopped)
 715                                                .tooltip({
 716                                                    let focus_handle = focus_handle.clone();
 717                                                    move |window, cx| {
 718                                                        Tooltip::for_action_in(
 719                                                            "Continue program",
 720                                                            &Continue,
 721                                                            &focus_handle,
 722                                                            window,
 723                                                            cx,
 724                                                        )
 725                                                    }
 726                                                }),
 727                                            )
 728                                        }
 729                                    })
 730                                    .child(
 731                                        IconButton::new("debug-step-over", IconName::ArrowRight)
 732                                            .icon_size(IconSize::XSmall)
 733                                            .shape(ui::IconButtonShape::Square)
 734                                            .on_click(window.listener_for(
 735                                                &running_session,
 736                                                |this, _, _window, cx| {
 737                                                    this.step_over(cx);
 738                                                },
 739                                            ))
 740                                            .disabled(thread_status != ThreadStatus::Stopped)
 741                                            .tooltip({
 742                                                let focus_handle = focus_handle.clone();
 743                                                move |window, cx| {
 744                                                    Tooltip::for_action_in(
 745                                                        "Step over",
 746                                                        &StepOver,
 747                                                        &focus_handle,
 748                                                        window,
 749                                                        cx,
 750                                                    )
 751                                                }
 752                                            }),
 753                                    )
 754                                    .child(
 755                                        IconButton::new("debug-step-out", IconName::ArrowUpRight)
 756                                            .icon_size(IconSize::XSmall)
 757                                            .shape(ui::IconButtonShape::Square)
 758                                            .on_click(window.listener_for(
 759                                                &running_session,
 760                                                |this, _, _window, cx| {
 761                                                    this.step_out(cx);
 762                                                },
 763                                            ))
 764                                            .disabled(thread_status != ThreadStatus::Stopped)
 765                                            .tooltip({
 766                                                let focus_handle = focus_handle.clone();
 767                                                move |window, cx| {
 768                                                    Tooltip::for_action_in(
 769                                                        "Step out",
 770                                                        &StepOut,
 771                                                        &focus_handle,
 772                                                        window,
 773                                                        cx,
 774                                                    )
 775                                                }
 776                                            }),
 777                                    )
 778                                    .child(
 779                                        IconButton::new(
 780                                            "debug-step-into",
 781                                            IconName::ArrowDownRight,
 782                                        )
 783                                        .icon_size(IconSize::XSmall)
 784                                        .shape(ui::IconButtonShape::Square)
 785                                        .on_click(window.listener_for(
 786                                            &running_session,
 787                                            |this, _, _window, cx| {
 788                                                this.step_in(cx);
 789                                            },
 790                                        ))
 791                                        .disabled(thread_status != ThreadStatus::Stopped)
 792                                        .tooltip({
 793                                            let focus_handle = focus_handle.clone();
 794                                            move |window, cx| {
 795                                                Tooltip::for_action_in(
 796                                                    "Step in",
 797                                                    &StepInto,
 798                                                    &focus_handle,
 799                                                    window,
 800                                                    cx,
 801                                                )
 802                                            }
 803                                        }),
 804                                    )
 805                                    .child(Divider::vertical())
 806                                    .child(
 807                                        IconButton::new(
 808                                            "debug-enable-breakpoint",
 809                                            IconName::DebugDisabledBreakpoint,
 810                                        )
 811                                        .icon_size(IconSize::XSmall)
 812                                        .shape(ui::IconButtonShape::Square)
 813                                        .disabled(thread_status != ThreadStatus::Stopped),
 814                                    )
 815                                    .child(
 816                                        IconButton::new(
 817                                            "debug-disable-breakpoint",
 818                                            IconName::CircleOff,
 819                                        )
 820                                        .icon_size(IconSize::XSmall)
 821                                        .shape(ui::IconButtonShape::Square)
 822                                        .disabled(thread_status != ThreadStatus::Stopped),
 823                                    )
 824                                    .child(
 825                                        IconButton::new(
 826                                            "debug-disable-all-breakpoints",
 827                                            IconName::BugOff,
 828                                        )
 829                                        .icon_size(IconSize::XSmall)
 830                                        .shape(ui::IconButtonShape::Square)
 831                                        .disabled(
 832                                            thread_status == ThreadStatus::Exited
 833                                                || thread_status == ThreadStatus::Ended,
 834                                        )
 835                                        .on_click(window.listener_for(
 836                                            &running_session,
 837                                            |this, _, _window, cx| {
 838                                                this.toggle_ignore_breakpoints(cx);
 839                                            },
 840                                        ))
 841                                        .tooltip({
 842                                            let focus_handle = focus_handle.clone();
 843                                            move |window, cx| {
 844                                                Tooltip::for_action_in(
 845                                                    "Disable all breakpoints",
 846                                                    &ToggleIgnoreBreakpoints,
 847                                                    &focus_handle,
 848                                                    window,
 849                                                    cx,
 850                                                )
 851                                            }
 852                                        }),
 853                                    )
 854                                    .child(Divider::vertical())
 855                                    .child(
 856                                        IconButton::new("debug-restart", IconName::DebugRestart)
 857                                            .icon_size(IconSize::XSmall)
 858                                            .on_click(window.listener_for(
 859                                                &running_session,
 860                                                |this, _, _window, cx| {
 861                                                    this.restart_session(cx);
 862                                                },
 863                                            ))
 864                                            .tooltip({
 865                                                let focus_handle = focus_handle.clone();
 866                                                move |window, cx| {
 867                                                    Tooltip::for_action_in(
 868                                                        "Restart",
 869                                                        &Restart,
 870                                                        &focus_handle,
 871                                                        window,
 872                                                        cx,
 873                                                    )
 874                                                }
 875                                            }),
 876                                    )
 877                                    .child(
 878                                        IconButton::new("debug-stop", IconName::Power)
 879                                            .icon_size(IconSize::XSmall)
 880                                            .on_click(window.listener_for(
 881                                                &running_session,
 882                                                |this, _, _window, cx| {
 883                                                    this.stop_thread(cx);
 884                                                },
 885                                            ))
 886                                            .disabled(
 887                                                thread_status != ThreadStatus::Stopped
 888                                                    && thread_status != ThreadStatus::Running,
 889                                            )
 890                                            .tooltip({
 891                                                let focus_handle = focus_handle.clone();
 892                                                let label = if capabilities
 893                                                    .supports_terminate_threads_request
 894                                                    .unwrap_or_default()
 895                                                {
 896                                                    "Terminate Thread"
 897                                                } else {
 898                                                    "Terminate All Threads"
 899                                                };
 900                                                move |window, cx| {
 901                                                    Tooltip::for_action_in(
 902                                                        label,
 903                                                        &Stop,
 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(
1262                                                CreateDebuggingSession.boxed_clone(),
1263                                                cx,
1264                                            );
1265                                        }),
1266                                ),
1267                            ),
1268                    )
1269                }
1270            })
1271            .into_any()
1272    }
1273}
1274
1275struct DebuggerProvider(Entity<DebugPanel>);
1276
1277impl workspace::DebuggerProvider for DebuggerProvider {
1278    fn start_session(
1279        &self,
1280        definition: DebugScenario,
1281        context: TaskContext,
1282        buffer: Option<Entity<Buffer>>,
1283        window: &mut Window,
1284        cx: &mut App,
1285    ) {
1286        self.0.update(cx, |_, cx| {
1287            cx.defer_in(window, |this, window, cx| {
1288                this.start_session(definition, context, buffer, None, window, cx);
1289            })
1290        })
1291    }
1292}