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