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                        let workspace = workspace.clone();
 625                        window
 626                            .spawn(cx, async move |cx| {
 627                                let task_contexts = workspace
 628                                    .update_in(cx, |workspace, window, cx| {
 629                                        tasks_ui::task_contexts(workspace, window, cx)
 630                                    })?
 631                                    .await;
 632
 633                                workspace.update_in(cx, |this, window, cx| {
 634                                    this.toggle_modal(window, cx, |window, cx| {
 635                                        NewSessionModal::new(
 636                                            past_debug_definition,
 637                                            weak_panel,
 638                                            workspace.clone(),
 639                                            None,
 640                                            task_contexts,
 641                                            window,
 642                                            cx,
 643                                        )
 644                                    });
 645                                })?;
 646
 647                                Result::<_, anyhow::Error>::Ok(())
 648                            })
 649                            .detach();
 650                    }
 651                })
 652                .tooltip({
 653                    let focus_handle = focus_handle.clone();
 654                    move |window, cx| {
 655                        Tooltip::for_action_in(
 656                            "New Debug Session",
 657                            &CreateDebuggingSession,
 658                            &focus_handle,
 659                            window,
 660                            cx,
 661                        )
 662                    }
 663                })
 664        };
 665
 666        Some(
 667            div.border_b_1()
 668                .border_color(cx.theme().colors().border)
 669                .p_1()
 670                .justify_between()
 671                .w_full()
 672                .when(is_side, |this| this.gap_1())
 673                .child(
 674                    h_flex()
 675                        .child(
 676                            h_flex().gap_2().w_full().when_some(
 677                                active_session
 678                                    .as_ref()
 679                                    .map(|session| session.read(cx).running_state()),
 680                                |this, running_session| {
 681                                    let thread_status =
 682                                        running_session.read(cx).thread_status(cx).unwrap_or(
 683                                            project::debugger::session::ThreadStatus::Exited,
 684                                        );
 685                                    let capabilities = running_session.read(cx).capabilities(cx);
 686                                    this.map(|this| {
 687                                        if thread_status == ThreadStatus::Running {
 688                                            this.child(
 689                                                IconButton::new(
 690                                                    "debug-pause",
 691                                                    IconName::DebugPause,
 692                                                )
 693                                                .icon_size(IconSize::XSmall)
 694                                                .shape(ui::IconButtonShape::Square)
 695                                                .on_click(window.listener_for(
 696                                                    &running_session,
 697                                                    |this, _, _window, cx| {
 698                                                        this.pause_thread(cx);
 699                                                    },
 700                                                ))
 701                                                .tooltip({
 702                                                    let focus_handle = focus_handle.clone();
 703                                                    move |window, cx| {
 704                                                        Tooltip::for_action_in(
 705                                                            "Pause program",
 706                                                            &Pause,
 707                                                            &focus_handle,
 708                                                            window,
 709                                                            cx,
 710                                                        )
 711                                                    }
 712                                                }),
 713                                            )
 714                                        } else {
 715                                            this.child(
 716                                                IconButton::new(
 717                                                    "debug-continue",
 718                                                    IconName::DebugContinue,
 719                                                )
 720                                                .icon_size(IconSize::XSmall)
 721                                                .shape(ui::IconButtonShape::Square)
 722                                                .on_click(window.listener_for(
 723                                                    &running_session,
 724                                                    |this, _, _window, cx| this.continue_thread(cx),
 725                                                ))
 726                                                .disabled(thread_status != ThreadStatus::Stopped)
 727                                                .tooltip({
 728                                                    let focus_handle = focus_handle.clone();
 729                                                    move |window, cx| {
 730                                                        Tooltip::for_action_in(
 731                                                            "Continue program",
 732                                                            &Continue,
 733                                                            &focus_handle,
 734                                                            window,
 735                                                            cx,
 736                                                        )
 737                                                    }
 738                                                }),
 739                                            )
 740                                        }
 741                                    })
 742                                    .child(
 743                                        IconButton::new("debug-step-over", IconName::ArrowRight)
 744                                            .icon_size(IconSize::XSmall)
 745                                            .shape(ui::IconButtonShape::Square)
 746                                            .on_click(window.listener_for(
 747                                                &running_session,
 748                                                |this, _, _window, cx| {
 749                                                    this.step_over(cx);
 750                                                },
 751                                            ))
 752                                            .disabled(thread_status != ThreadStatus::Stopped)
 753                                            .tooltip({
 754                                                let focus_handle = focus_handle.clone();
 755                                                move |window, cx| {
 756                                                    Tooltip::for_action_in(
 757                                                        "Step over",
 758                                                        &StepOver,
 759                                                        &focus_handle,
 760                                                        window,
 761                                                        cx,
 762                                                    )
 763                                                }
 764                                            }),
 765                                    )
 766                                    .child(
 767                                        IconButton::new("debug-step-out", IconName::ArrowUpRight)
 768                                            .icon_size(IconSize::XSmall)
 769                                            .shape(ui::IconButtonShape::Square)
 770                                            .on_click(window.listener_for(
 771                                                &running_session,
 772                                                |this, _, _window, cx| {
 773                                                    this.step_out(cx);
 774                                                },
 775                                            ))
 776                                            .disabled(thread_status != ThreadStatus::Stopped)
 777                                            .tooltip({
 778                                                let focus_handle = focus_handle.clone();
 779                                                move |window, cx| {
 780                                                    Tooltip::for_action_in(
 781                                                        "Step out",
 782                                                        &StepOut,
 783                                                        &focus_handle,
 784                                                        window,
 785                                                        cx,
 786                                                    )
 787                                                }
 788                                            }),
 789                                    )
 790                                    .child(
 791                                        IconButton::new(
 792                                            "debug-step-into",
 793                                            IconName::ArrowDownRight,
 794                                        )
 795                                        .icon_size(IconSize::XSmall)
 796                                        .shape(ui::IconButtonShape::Square)
 797                                        .on_click(window.listener_for(
 798                                            &running_session,
 799                                            |this, _, _window, cx| {
 800                                                this.step_in(cx);
 801                                            },
 802                                        ))
 803                                        .disabled(thread_status != ThreadStatus::Stopped)
 804                                        .tooltip({
 805                                            let focus_handle = focus_handle.clone();
 806                                            move |window, cx| {
 807                                                Tooltip::for_action_in(
 808                                                    "Step in",
 809                                                    &StepInto,
 810                                                    &focus_handle,
 811                                                    window,
 812                                                    cx,
 813                                                )
 814                                            }
 815                                        }),
 816                                    )
 817                                    .child(Divider::vertical())
 818                                    .child(
 819                                        IconButton::new(
 820                                            "debug-enable-breakpoint",
 821                                            IconName::DebugDisabledBreakpoint,
 822                                        )
 823                                        .icon_size(IconSize::XSmall)
 824                                        .shape(ui::IconButtonShape::Square)
 825                                        .disabled(thread_status != ThreadStatus::Stopped),
 826                                    )
 827                                    .child(
 828                                        IconButton::new(
 829                                            "debug-disable-breakpoint",
 830                                            IconName::CircleOff,
 831                                        )
 832                                        .icon_size(IconSize::XSmall)
 833                                        .shape(ui::IconButtonShape::Square)
 834                                        .disabled(thread_status != ThreadStatus::Stopped),
 835                                    )
 836                                    .child(
 837                                        IconButton::new(
 838                                            "debug-disable-all-breakpoints",
 839                                            IconName::BugOff,
 840                                        )
 841                                        .icon_size(IconSize::XSmall)
 842                                        .shape(ui::IconButtonShape::Square)
 843                                        .disabled(
 844                                            thread_status == ThreadStatus::Exited
 845                                                || thread_status == ThreadStatus::Ended,
 846                                        )
 847                                        .on_click(window.listener_for(
 848                                            &running_session,
 849                                            |this, _, _window, cx| {
 850                                                this.toggle_ignore_breakpoints(cx);
 851                                            },
 852                                        ))
 853                                        .tooltip({
 854                                            let focus_handle = focus_handle.clone();
 855                                            move |window, cx| {
 856                                                Tooltip::for_action_in(
 857                                                    "Disable all breakpoints",
 858                                                    &ToggleIgnoreBreakpoints,
 859                                                    &focus_handle,
 860                                                    window,
 861                                                    cx,
 862                                                )
 863                                            }
 864                                        }),
 865                                    )
 866                                    .child(Divider::vertical())
 867                                    .child(
 868                                        IconButton::new("debug-restart", IconName::DebugRestart)
 869                                            .icon_size(IconSize::XSmall)
 870                                            .on_click(window.listener_for(
 871                                                &running_session,
 872                                                |this, _, _window, cx| {
 873                                                    this.restart_session(cx);
 874                                                },
 875                                            ))
 876                                            .tooltip({
 877                                                let focus_handle = focus_handle.clone();
 878                                                move |window, cx| {
 879                                                    Tooltip::for_action_in(
 880                                                        "Restart",
 881                                                        &Restart,
 882                                                        &focus_handle,
 883                                                        window,
 884                                                        cx,
 885                                                    )
 886                                                }
 887                                            }),
 888                                    )
 889                                    .child(
 890                                        IconButton::new("debug-stop", IconName::Power)
 891                                            .icon_size(IconSize::XSmall)
 892                                            .on_click(window.listener_for(
 893                                                &running_session,
 894                                                |this, _, _window, cx| {
 895                                                    this.stop_thread(cx);
 896                                                },
 897                                            ))
 898                                            .disabled(
 899                                                thread_status != ThreadStatus::Stopped
 900                                                    && thread_status != ThreadStatus::Running,
 901                                            )
 902                                            .tooltip({
 903                                                let focus_handle = focus_handle.clone();
 904                                                let label = if capabilities
 905                                                    .supports_terminate_threads_request
 906                                                    .unwrap_or_default()
 907                                                {
 908                                                    "Terminate Thread"
 909                                                } else {
 910                                                    "Terminate All Threads"
 911                                                };
 912                                                move |window, cx| {
 913                                                    Tooltip::for_action_in(
 914                                                        label,
 915                                                        &Stop,
 916                                                        &focus_handle,
 917                                                        window,
 918                                                        cx,
 919                                                    )
 920                                                }
 921                                            }),
 922                                    )
 923                                },
 924                            ),
 925                        )
 926                        .justify_around()
 927                        .when(is_side, |this| this.child(new_session_button())),
 928                )
 929                .child(
 930                    h_flex()
 931                        .gap_2()
 932                        .when(is_side, |this| this.justify_between())
 933                        .child(
 934                            h_flex().when_some(
 935                                active_session
 936                                    .as_ref()
 937                                    .map(|session| session.read(cx).running_state())
 938                                    .cloned(),
 939                                |this, session| {
 940                                    this.child(
 941                                        session.update(cx, |this, cx| {
 942                                            this.thread_dropdown(window, cx)
 943                                        }),
 944                                    )
 945                                    .when(!is_side, |this| this.gap_2().child(Divider::vertical()))
 946                                },
 947                            ),
 948                        )
 949                        .child(
 950                            h_flex()
 951                                .when_some(active_session.as_ref(), |this, session| {
 952                                    let context_menu =
 953                                        self.sessions_drop_down_menu(session, window, cx);
 954                                    this.child(context_menu).gap_2().child(Divider::vertical())
 955                                })
 956                                .when(!is_side, |this| this.child(new_session_button())),
 957                        ),
 958                ),
 959        )
 960    }
 961
 962    fn activate_pane_in_direction(
 963        &mut self,
 964        direction: SplitDirection,
 965        window: &mut Window,
 966        cx: &mut Context<Self>,
 967    ) {
 968        if let Some(session) = self.active_session() {
 969            session.update(cx, |session, cx| {
 970                session.running_state().update(cx, |running, cx| {
 971                    running.activate_pane_in_direction(direction, window, cx);
 972                })
 973            });
 974        }
 975    }
 976
 977    fn activate_item(
 978        &mut self,
 979        item: DebuggerPaneItem,
 980        window: &mut Window,
 981        cx: &mut Context<Self>,
 982    ) {
 983        if let Some(session) = self.active_session() {
 984            session.update(cx, |session, cx| {
 985                session.running_state().update(cx, |running, cx| {
 986                    running.activate_item(item, window, cx);
 987                });
 988            });
 989        }
 990    }
 991
 992    fn activate_session(
 993        &mut self,
 994        session_item: Entity<DebugSession>,
 995        window: &mut Window,
 996        cx: &mut Context<Self>,
 997    ) {
 998        debug_assert!(self.sessions.contains(&session_item));
 999        session_item.focus_handle(cx).focus(window);
1000        session_item.update(cx, |this, cx| {
1001            this.running_state().update(cx, |this, cx| {
1002                this.go_to_selected_stack_frame(window, cx);
1003            });
1004        });
1005        self.active_session = Some(session_item);
1006        cx.notify();
1007    }
1008}
1009
1010impl EventEmitter<PanelEvent> for DebugPanel {}
1011impl EventEmitter<DebugPanelEvent> for DebugPanel {}
1012
1013impl Focusable for DebugPanel {
1014    fn focus_handle(&self, _: &App) -> FocusHandle {
1015        self.focus_handle.clone()
1016    }
1017}
1018
1019impl Panel for DebugPanel {
1020    fn persistent_name() -> &'static str {
1021        "DebugPanel"
1022    }
1023
1024    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1025        match DebuggerSettings::get_global(cx).dock {
1026            DebugPanelDockPosition::Left => DockPosition::Left,
1027            DebugPanelDockPosition::Bottom => DockPosition::Bottom,
1028            DebugPanelDockPosition::Right => DockPosition::Right,
1029        }
1030    }
1031
1032    fn position_is_valid(&self, _: DockPosition) -> bool {
1033        true
1034    }
1035
1036    fn set_position(
1037        &mut self,
1038        position: DockPosition,
1039        window: &mut Window,
1040        cx: &mut Context<Self>,
1041    ) {
1042        if position.axis() != self.position(window, cx).axis() {
1043            self.sessions.iter().for_each(|session_item| {
1044                session_item.update(cx, |item, cx| {
1045                    item.running_state()
1046                        .update(cx, |state, _| state.invert_axies())
1047                })
1048            });
1049        }
1050
1051        settings::update_settings_file::<DebuggerSettings>(
1052            self.fs.clone(),
1053            cx,
1054            move |settings, _| {
1055                let dock = match position {
1056                    DockPosition::Left => DebugPanelDockPosition::Left,
1057                    DockPosition::Bottom => DebugPanelDockPosition::Bottom,
1058                    DockPosition::Right => DebugPanelDockPosition::Right,
1059                };
1060                settings.dock = dock;
1061            },
1062        );
1063    }
1064
1065    fn size(&self, _window: &Window, _: &App) -> Pixels {
1066        self.size
1067    }
1068
1069    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
1070        self.size = size.unwrap();
1071    }
1072
1073    fn remote_id() -> Option<proto::PanelId> {
1074        Some(proto::PanelId::DebugPanel)
1075    }
1076
1077    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
1078        Some(IconName::Debug)
1079    }
1080
1081    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1082        if DebuggerSettings::get_global(cx).button {
1083            Some("Debug Panel")
1084        } else {
1085            None
1086        }
1087    }
1088
1089    fn toggle_action(&self) -> Box<dyn Action> {
1090        Box::new(ToggleFocus)
1091    }
1092
1093    fn pane(&self) -> Option<Entity<Pane>> {
1094        None
1095    }
1096
1097    fn activation_priority(&self) -> u32 {
1098        9
1099    }
1100
1101    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1102}
1103
1104impl Render for DebugPanel {
1105    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1106        let has_sessions = self.sessions.len() > 0;
1107        let this = cx.weak_entity();
1108        debug_assert_eq!(has_sessions, self.active_session.is_some());
1109
1110        if self
1111            .active_session
1112            .as_ref()
1113            .map(|session| session.read(cx).running_state())
1114            .map(|state| state.read(cx).has_open_context_menu(cx))
1115            .unwrap_or(false)
1116        {
1117            self.context_menu.take();
1118        }
1119
1120        v_flex()
1121            .size_full()
1122            .key_context("DebugPanel")
1123            .child(h_flex().children(self.top_controls_strip(window, cx)))
1124            .track_focus(&self.focus_handle(cx))
1125            .on_action({
1126                let this = this.clone();
1127                move |_: &workspace::ActivatePaneLeft, window, cx| {
1128                    this.update(cx, |this, cx| {
1129                        this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1130                    })
1131                    .ok();
1132                }
1133            })
1134            .on_action({
1135                let this = this.clone();
1136                move |_: &workspace::ActivatePaneRight, window, cx| {
1137                    this.update(cx, |this, cx| {
1138                        this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1139                    })
1140                    .ok();
1141                }
1142            })
1143            .on_action({
1144                let this = this.clone();
1145                move |_: &workspace::ActivatePaneUp, window, cx| {
1146                    this.update(cx, |this, cx| {
1147                        this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1148                    })
1149                    .ok();
1150                }
1151            })
1152            .on_action({
1153                let this = this.clone();
1154                move |_: &workspace::ActivatePaneDown, window, cx| {
1155                    this.update(cx, |this, cx| {
1156                        this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1157                    })
1158                    .ok();
1159                }
1160            })
1161            .on_action({
1162                let this = this.clone();
1163                move |_: &FocusConsole, window, cx| {
1164                    this.update(cx, |this, cx| {
1165                        this.activate_item(DebuggerPaneItem::Console, window, cx);
1166                    })
1167                    .ok();
1168                }
1169            })
1170            .on_action({
1171                let this = this.clone();
1172                move |_: &FocusVariables, window, cx| {
1173                    this.update(cx, |this, cx| {
1174                        this.activate_item(DebuggerPaneItem::Variables, window, cx);
1175                    })
1176                    .ok();
1177                }
1178            })
1179            .on_action({
1180                let this = this.clone();
1181                move |_: &FocusBreakpointList, window, cx| {
1182                    this.update(cx, |this, cx| {
1183                        this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1184                    })
1185                    .ok();
1186                }
1187            })
1188            .on_action({
1189                let this = this.clone();
1190                move |_: &FocusFrames, window, cx| {
1191                    this.update(cx, |this, cx| {
1192                        this.activate_item(DebuggerPaneItem::Frames, window, cx);
1193                    })
1194                    .ok();
1195                }
1196            })
1197            .on_action({
1198                let this = this.clone();
1199                move |_: &FocusModules, window, cx| {
1200                    this.update(cx, |this, cx| {
1201                        this.activate_item(DebuggerPaneItem::Modules, window, cx);
1202                    })
1203                    .ok();
1204                }
1205            })
1206            .on_action({
1207                let this = this.clone();
1208                move |_: &FocusLoadedSources, window, cx| {
1209                    this.update(cx, |this, cx| {
1210                        this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1211                    })
1212                    .ok();
1213                }
1214            })
1215            .on_action({
1216                let this = this.clone();
1217                move |_: &FocusTerminal, window, cx| {
1218                    this.update(cx, |this, cx| {
1219                        this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1220                    })
1221                    .ok();
1222                }
1223            })
1224            .when(self.active_session.is_some(), |this| {
1225                this.on_mouse_down(
1226                    MouseButton::Right,
1227                    cx.listener(|this, event: &MouseDownEvent, window, cx| {
1228                        if this
1229                            .active_session
1230                            .as_ref()
1231                            .map(|session| {
1232                                let state = session.read(cx).running_state();
1233                                state.read(cx).has_pane_at_position(event.position)
1234                            })
1235                            .unwrap_or(false)
1236                        {
1237                            this.deploy_context_menu(event.position, window, cx);
1238                        }
1239                    }),
1240                )
1241                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1242                    deferred(
1243                        anchored()
1244                            .position(*position)
1245                            .anchor(gpui::Corner::TopLeft)
1246                            .child(menu.clone()),
1247                    )
1248                    .with_priority(1)
1249                }))
1250            })
1251            .map(|this| {
1252                if has_sessions {
1253                    this.children(self.active_session.clone())
1254                } else {
1255                    this.child(
1256                        v_flex()
1257                            .h_full()
1258                            .gap_1()
1259                            .items_center()
1260                            .justify_center()
1261                            .child(
1262                                h_flex().child(
1263                                    Label::new("No Debugging Sessions")
1264                                        .size(LabelSize::Small)
1265                                        .color(Color::Muted),
1266                                ),
1267                            )
1268                            .child(
1269                                h_flex().flex_shrink().child(
1270                                    Button::new("spawn-new-session-empty-state", "New Session")
1271                                        .size(ButtonSize::Large)
1272                                        .on_click(|_, window, cx| {
1273                                            window.dispatch_action(
1274                                                CreateDebuggingSession.boxed_clone(),
1275                                                cx,
1276                                            );
1277                                        }),
1278                                ),
1279                            ),
1280                    )
1281                }
1282            })
1283            .into_any()
1284    }
1285}
1286
1287struct DebuggerProvider(Entity<DebugPanel>);
1288
1289impl workspace::DebuggerProvider for DebuggerProvider {
1290    fn start_session(
1291        &self,
1292        definition: DebugScenario,
1293        context: TaskContext,
1294        buffer: Option<Entity<Buffer>>,
1295        window: &mut Window,
1296        cx: &mut App,
1297    ) {
1298        self.0.update(cx, |_, cx| {
1299            cx.defer_in(window, |this, window, cx| {
1300                this.start_session(definition, context, buffer, None, window, cx);
1301            })
1302        })
1303    }
1304}