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