running.rs

   1pub(crate) mod breakpoint_list;
   2pub(crate) mod console;
   3pub(crate) mod loaded_source_list;
   4pub(crate) mod module_list;
   5pub mod stack_frame_list;
   6pub mod variable_list;
   7
   8use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
   9
  10use crate::persistence::{self, DebuggerPaneItem, SerializedLayout};
  11
  12use super::DebugPanelItemEvent;
  13use anyhow::{Context as _, Result, anyhow};
  14use breakpoint_list::BreakpointList;
  15use collections::{HashMap, IndexMap};
  16use console::Console;
  17use dap::{
  18    Capabilities, DapRegistry, RunInTerminalRequestArguments, Thread,
  19    adapters::{DebugAdapterName, DebugTaskDefinition},
  20    client::SessionId,
  21    debugger_settings::DebuggerSettings,
  22};
  23use futures::{SinkExt, channel::mpsc};
  24use gpui::{
  25    Action as _, AnyView, AppContext, Axis, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
  26    NoAction, Pixels, Point, Subscription, Task, WeakEntity,
  27};
  28use language::Buffer;
  29use loaded_source_list::LoadedSourceList;
  30use module_list::ModuleList;
  31use project::{
  32    Project, WorktreeId,
  33    debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
  34    terminals::TerminalKind,
  35};
  36use rpc::proto::ViewId;
  37use serde_json::Value;
  38use settings::Settings;
  39use stack_frame_list::StackFrameList;
  40use task::{
  41    BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskContext, ZedDebugConfig,
  42    substitute_variables_in_str,
  43};
  44use terminal_view::TerminalView;
  45use ui::{
  46    ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, FluentBuilder,
  47    IconButton, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon as _,
  48    ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Tab, Tooltip,
  49    VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,
  50};
  51use util::ResultExt;
  52use variable_list::VariableList;
  53use workspace::{
  54    ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, SplitDirection,
  55    Workspace, item::TabContentParams, move_item, pane::Event,
  56};
  57
  58pub struct RunningState {
  59    session: Entity<Session>,
  60    thread_id: Option<ThreadId>,
  61    focus_handle: FocusHandle,
  62    _remote_id: Option<ViewId>,
  63    workspace: WeakEntity<Workspace>,
  64    session_id: SessionId,
  65    variable_list: Entity<variable_list::VariableList>,
  66    _subscriptions: Vec<Subscription>,
  67    stack_frame_list: Entity<stack_frame_list::StackFrameList>,
  68    loaded_sources_list: Entity<LoadedSourceList>,
  69    pub debug_terminal: Entity<DebugTerminal>,
  70    module_list: Entity<module_list::ModuleList>,
  71    console: Entity<Console>,
  72    breakpoint_list: Entity<BreakpointList>,
  73    panes: PaneGroup,
  74    active_pane: Option<Entity<Pane>>,
  75    pane_close_subscriptions: HashMap<EntityId, Subscription>,
  76    dock_axis: Axis,
  77    _schedule_serialize: Option<Task<()>>,
  78}
  79
  80impl RunningState {
  81    pub(crate) fn thread_id(&self) -> Option<ThreadId> {
  82        self.thread_id
  83    }
  84
  85    pub(crate) fn active_pane(&self) -> Option<&Entity<Pane>> {
  86        self.active_pane.as_ref()
  87    }
  88}
  89
  90impl Render for RunningState {
  91    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
  92        let zoomed_pane = self
  93            .panes
  94            .panes()
  95            .into_iter()
  96            .find(|pane| pane.read(cx).is_zoomed());
  97
  98        let active = self.panes.panes().into_iter().next();
  99        let pane = if let Some(ref zoomed_pane) = zoomed_pane {
 100            zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element())
 101        } else if let Some(active) = active {
 102            self.panes
 103                .render(
 104                    None,
 105                    &ActivePaneDecorator::new(active, &self.workspace),
 106                    window,
 107                    cx,
 108                )
 109                .into_any_element()
 110        } else {
 111            div().into_any_element()
 112        };
 113        let thread_status = self
 114            .thread_id
 115            .map(|thread_id| self.session.read(cx).thread_status(thread_id))
 116            .unwrap_or(ThreadStatus::Exited);
 117
 118        self.variable_list.update(cx, |this, cx| {
 119            this.disabled(thread_status != ThreadStatus::Stopped, cx);
 120        });
 121        v_flex()
 122            .size_full()
 123            .key_context("DebugSessionItem")
 124            .track_focus(&self.focus_handle(cx))
 125            .child(h_flex().flex_1().child(pane))
 126    }
 127}
 128
 129pub(crate) struct SubView {
 130    inner: AnyView,
 131    item_focus_handle: FocusHandle,
 132    kind: DebuggerPaneItem,
 133    show_indicator: Box<dyn Fn(&App) -> bool>,
 134    hovered: bool,
 135}
 136
 137impl SubView {
 138    pub(crate) fn new(
 139        item_focus_handle: FocusHandle,
 140        view: AnyView,
 141        kind: DebuggerPaneItem,
 142        show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
 143        cx: &mut App,
 144    ) -> Entity<Self> {
 145        cx.new(|_| Self {
 146            kind,
 147            inner: view,
 148            item_focus_handle,
 149            show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
 150            hovered: false,
 151        })
 152    }
 153
 154    pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
 155        self.kind
 156    }
 157}
 158impl Focusable for SubView {
 159    fn focus_handle(&self, _: &App) -> FocusHandle {
 160        self.item_focus_handle.clone()
 161    }
 162}
 163impl EventEmitter<()> for SubView {}
 164impl Item for SubView {
 165    type Event = ();
 166
 167    /// This is used to serialize debugger pane layouts
 168    /// A SharedString gets converted to a enum and back during serialization/deserialization.
 169    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
 170        self.kind.to_shared_string()
 171    }
 172
 173    fn tab_content(
 174        &self,
 175        params: workspace::item::TabContentParams,
 176        _: &Window,
 177        cx: &App,
 178    ) -> AnyElement {
 179        let label = Label::new(self.kind.to_shared_string())
 180            .size(ui::LabelSize::Small)
 181            .color(params.text_color())
 182            .line_height_style(ui::LineHeightStyle::UiLabel);
 183
 184        if !params.selected && self.show_indicator.as_ref()(cx) {
 185            return h_flex()
 186                .justify_between()
 187                .child(ui::Indicator::dot())
 188                .gap_2()
 189                .child(label)
 190                .into_any_element();
 191        }
 192
 193        label.into_any_element()
 194    }
 195}
 196
 197impl Render for SubView {
 198    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 199        v_flex()
 200            .id(SharedString::from(format!(
 201                "subview-container-{}",
 202                self.kind.to_shared_string()
 203            )))
 204            .on_hover(cx.listener(|this, hovered, _, cx| {
 205                this.hovered = *hovered;
 206                cx.notify();
 207            }))
 208            .size_full()
 209            // Add border unconditionally to prevent layout shifts on focus changes.
 210            .border_1()
 211            .when(self.item_focus_handle.contains_focused(window, cx), |el| {
 212                el.border_color(cx.theme().colors().pane_focused_border)
 213            })
 214            .child(self.inner.clone())
 215    }
 216}
 217
 218pub(crate) fn new_debugger_pane(
 219    workspace: WeakEntity<Workspace>,
 220    project: Entity<Project>,
 221    window: &mut Window,
 222    cx: &mut Context<RunningState>,
 223) -> Entity<Pane> {
 224    let weak_running = cx.weak_entity();
 225    let custom_drop_handle = {
 226        let workspace = workspace.clone();
 227        let project = project.downgrade();
 228        let weak_running = weak_running.clone();
 229        move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {
 230            let Some(tab) = any.downcast_ref::<DraggedTab>() else {
 231                return ControlFlow::Break(());
 232            };
 233            let Some(project) = project.upgrade() else {
 234                return ControlFlow::Break(());
 235            };
 236            let this_pane = cx.entity().clone();
 237            let item = if tab.pane == this_pane {
 238                pane.item_for_index(tab.ix)
 239            } else {
 240                tab.pane.read(cx).item_for_index(tab.ix)
 241            };
 242            let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {
 243                return ControlFlow::Break(());
 244            };
 245
 246            let source = tab.pane.clone();
 247            let item_id_to_move = item.item_id();
 248
 249            let Ok(new_split_pane) = pane
 250                .drag_split_direction()
 251                .map(|split_direction| {
 252                    weak_running.update(cx, |running, cx| {
 253                        let new_pane =
 254                            new_debugger_pane(workspace.clone(), project.clone(), window, cx);
 255                        let _previous_subscription = running.pane_close_subscriptions.insert(
 256                            new_pane.entity_id(),
 257                            cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
 258                        );
 259                        debug_assert!(_previous_subscription.is_none());
 260                        running
 261                            .panes
 262                            .split(&this_pane, &new_pane, split_direction)?;
 263                        anyhow::Ok(new_pane)
 264                    })
 265                })
 266                .transpose()
 267            else {
 268                return ControlFlow::Break(());
 269            };
 270
 271            match new_split_pane.transpose() {
 272                // Source pane may be the one currently updated, so defer the move.
 273                Ok(Some(new_pane)) => cx
 274                    .spawn_in(window, async move |_, cx| {
 275                        cx.update(|window, cx| {
 276                            move_item(
 277                                &source,
 278                                &new_pane,
 279                                item_id_to_move,
 280                                new_pane.read(cx).active_item_index(),
 281                                window,
 282                                cx,
 283                            );
 284                        })
 285                        .ok();
 286                    })
 287                    .detach(),
 288                // If we drop into existing pane or current pane,
 289                // regular pane drop handler will take care of it,
 290                // using the right tab index for the operation.
 291                Ok(None) => return ControlFlow::Continue(()),
 292                err @ Err(_) => {
 293                    err.log_err();
 294                    return ControlFlow::Break(());
 295                }
 296            };
 297
 298            ControlFlow::Break(())
 299        }
 300    };
 301
 302    let ret = cx.new(move |cx| {
 303        let mut pane = Pane::new(
 304            workspace.clone(),
 305            project.clone(),
 306            Default::default(),
 307            None,
 308            NoAction.boxed_clone(),
 309            window,
 310            cx,
 311        );
 312        let focus_handle = pane.focus_handle(cx);
 313        pane.set_can_split(Some(Arc::new({
 314            let weak_running = weak_running.clone();
 315            move |pane, dragged_item, _window, cx| {
 316                if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
 317                    let is_current_pane = tab.pane == cx.entity();
 318                    let Some(can_drag_away) = weak_running
 319                        .update(cx, |running_state, _| {
 320                            let current_panes = running_state.panes.panes();
 321                            !current_panes.contains(&&tab.pane)
 322                                || current_panes.len() > 1
 323                                || (!is_current_pane || pane.items_len() > 1)
 324                        })
 325                        .ok()
 326                    else {
 327                        return false;
 328                    };
 329                    if can_drag_away {
 330                        let item = if is_current_pane {
 331                            pane.item_for_index(tab.ix)
 332                        } else {
 333                            tab.pane.read(cx).item_for_index(tab.ix)
 334                        };
 335                        if let Some(item) = item {
 336                            return item.downcast::<SubView>().is_some();
 337                        }
 338                    }
 339                }
 340                false
 341            }
 342        })));
 343        pane.display_nav_history_buttons(None);
 344        pane.set_custom_drop_handle(cx, custom_drop_handle);
 345        pane.set_should_display_tab_bar(|_, _| true);
 346        pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));
 347        pane.set_render_tab_bar(cx, {
 348            move |pane, window, cx| {
 349                let active_pane_item = pane.active_item();
 350                let pane_group_id: SharedString =
 351                    format!("pane-zoom-button-hover-{}", cx.entity_id()).into();
 352                let is_hovered = active_pane_item.as_ref().map_or(false, |item| {
 353                    item.downcast::<SubView>()
 354                        .map_or(false, |this| this.read(cx).hovered)
 355                });
 356                h_flex()
 357                    .group(pane_group_id.clone())
 358                    .justify_between()
 359                    .bg(cx.theme().colors().tab_bar_background)
 360                    .border_b_1()
 361                    .px_2()
 362                    .border_color(cx.theme().colors().border)
 363                    .track_focus(&focus_handle)
 364                    .on_action(|_: &menu::Cancel, window, cx| {
 365                        if cx.stop_active_drag(window) {
 366                            return;
 367                        } else {
 368                            cx.propagate();
 369                        }
 370                    })
 371                    .child(
 372                        h_flex()
 373                            .w_full()
 374                            .gap_1()
 375                            .h(Tab::container_height(cx))
 376                            .drag_over::<DraggedTab>(|bar, _, _, cx| {
 377                                bar.bg(cx.theme().colors().drop_target_background)
 378                            })
 379                            .on_drop(cx.listener(
 380                                move |this, dragged_tab: &DraggedTab, window, cx| {
 381                                    this.drag_split_direction = None;
 382                                    this.handle_tab_drop(dragged_tab, this.items_len(), window, cx)
 383                                },
 384                            ))
 385                            .children(pane.items().enumerate().map(|(ix, item)| {
 386                                let selected = active_pane_item
 387                                    .as_ref()
 388                                    .map_or(false, |active| active.item_id() == item.item_id());
 389                                let deemphasized = !pane.has_focus(window, cx);
 390                                let item_ = item.boxed_clone();
 391                                div()
 392                                    .id(SharedString::from(format!(
 393                                        "debugger_tab_{}",
 394                                        item.item_id().as_u64()
 395                                    )))
 396                                    .p_1()
 397                                    .rounded_md()
 398                                    .cursor_pointer()
 399                                    .map(|this| {
 400                                        let theme = cx.theme();
 401                                        if selected {
 402                                            let color = theme.colors().tab_active_background;
 403                                            let color = if deemphasized {
 404                                                color.opacity(0.5)
 405                                            } else {
 406                                                color
 407                                            };
 408                                            this.bg(color)
 409                                        } else {
 410                                            let hover_color = theme.colors().element_hover;
 411                                            this.hover(|style| style.bg(hover_color))
 412                                        }
 413                                    })
 414                                    .on_click(cx.listener(move |this, _, window, cx| {
 415                                        let index = this.index_for_item(&*item_);
 416                                        if let Some(index) = index {
 417                                            this.activate_item(index, true, true, window, cx);
 418                                        }
 419                                    }))
 420                                    .child(item.tab_content(
 421                                        TabContentParams {
 422                                            selected,
 423                                            deemphasized,
 424                                            ..Default::default()
 425                                        },
 426                                        window,
 427                                        cx,
 428                                    ))
 429                                    .on_drop(cx.listener(
 430                                        move |this, dragged_tab: &DraggedTab, window, cx| {
 431                                            this.drag_split_direction = None;
 432                                            this.handle_tab_drop(dragged_tab, ix, window, cx)
 433                                        },
 434                                    ))
 435                                    .on_drag(
 436                                        DraggedTab {
 437                                            item: item.boxed_clone(),
 438                                            pane: cx.entity().clone(),
 439                                            detail: 0,
 440                                            is_active: selected,
 441                                            ix,
 442                                        },
 443                                        |tab, _, _, cx| cx.new(|_| tab.clone()),
 444                                    )
 445                            })),
 446                    )
 447                    .child({
 448                        let zoomed = pane.is_zoomed();
 449                        div()
 450                            .visible_on_hover(pane_group_id)
 451                            .when(is_hovered, |this| this.visible())
 452                            .child(
 453                                IconButton::new(
 454                                    SharedString::from(format!(
 455                                        "debug-toggle-zoom-{}",
 456                                        cx.entity_id()
 457                                    )),
 458                                    if zoomed {
 459                                        IconName::Minimize
 460                                    } else {
 461                                        IconName::Maximize
 462                                    },
 463                                )
 464                                .icon_size(IconSize::XSmall)
 465                                .on_click(cx.listener(move |pane, _, window, cx| {
 466                                    pane.toggle_zoom(&workspace::ToggleZoom, window, cx);
 467                                }))
 468                                .tooltip({
 469                                    let focus_handle = focus_handle.clone();
 470                                    move |window, cx| {
 471                                        let zoomed_text =
 472                                            if zoomed { "Zoom Out" } else { "Zoom In" };
 473                                        Tooltip::for_action_in(
 474                                            zoomed_text,
 475                                            &workspace::ToggleZoom,
 476                                            &focus_handle,
 477                                            window,
 478                                            cx,
 479                                        )
 480                                    }
 481                                }),
 482                            )
 483                    })
 484                    .into_any_element()
 485            }
 486        });
 487        pane
 488    });
 489
 490    ret
 491}
 492
 493pub struct DebugTerminal {
 494    pub terminal: Option<Entity<TerminalView>>,
 495    focus_handle: FocusHandle,
 496}
 497
 498impl DebugTerminal {
 499    fn empty(cx: &mut Context<Self>) -> Self {
 500        Self {
 501            terminal: None,
 502            focus_handle: cx.focus_handle(),
 503        }
 504    }
 505}
 506
 507impl gpui::Render for DebugTerminal {
 508    fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
 509        div()
 510            .size_full()
 511            .track_focus(&self.focus_handle)
 512            .children(self.terminal.clone())
 513    }
 514}
 515impl Focusable for DebugTerminal {
 516    fn focus_handle(&self, _cx: &App) -> FocusHandle {
 517        self.focus_handle.clone()
 518    }
 519}
 520
 521impl RunningState {
 522    // todo(debugger) move this to util and make it so you pass a closure to it that converts a string
 523    pub(crate) fn substitute_variables_in_config(
 524        config: &mut serde_json::Value,
 525        context: &TaskContext,
 526    ) {
 527        match config {
 528            serde_json::Value::Object(obj) => {
 529                obj.values_mut()
 530                    .for_each(|value| Self::substitute_variables_in_config(value, context));
 531            }
 532            serde_json::Value::Array(array) => {
 533                array
 534                    .iter_mut()
 535                    .for_each(|value| Self::substitute_variables_in_config(value, context));
 536            }
 537            serde_json::Value::String(s) => {
 538                if let Some(substituted) = substitute_variables_in_str(&s, context) {
 539                    *s = substituted;
 540                }
 541            }
 542            _ => {}
 543        }
 544    }
 545
 546    pub(crate) fn new(
 547        session: Entity<Session>,
 548        project: Entity<Project>,
 549        workspace: WeakEntity<Workspace>,
 550        serialized_pane_layout: Option<SerializedLayout>,
 551        dock_axis: Axis,
 552        window: &mut Window,
 553        cx: &mut Context<Self>,
 554    ) -> Self {
 555        let focus_handle = cx.focus_handle();
 556        let session_id = session.read(cx).session_id();
 557        let weak_state = cx.weak_entity();
 558        let stack_frame_list = cx.new(|cx| {
 559            StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx)
 560        });
 561
 562        let debug_terminal = cx.new(DebugTerminal::empty);
 563
 564        let variable_list =
 565            cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx));
 566
 567        let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
 568
 569        let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
 570
 571        let console = cx.new(|cx| {
 572            Console::new(
 573                session.clone(),
 574                stack_frame_list.clone(),
 575                variable_list.clone(),
 576                window,
 577                cx,
 578            )
 579        });
 580
 581        let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
 582
 583        let _subscriptions = vec![
 584            cx.observe(&module_list, |_, _, cx| cx.notify()),
 585            cx.subscribe_in(&session, window, |this, _, event, window, cx| {
 586                match event {
 587                    SessionEvent::Stopped(thread_id) => {
 588                        let panel = this
 589                            .workspace
 590                            .update(cx, |workspace, cx| {
 591                                workspace.open_panel::<crate::DebugPanel>(window, cx);
 592                                workspace.panel::<crate::DebugPanel>(cx)
 593                            })
 594                            .log_err()
 595                            .flatten();
 596
 597                        if let Some(thread_id) = thread_id {
 598                            this.select_thread(*thread_id, window, cx);
 599                        }
 600                        if let Some(panel) = panel {
 601                            let id = this.session_id;
 602                            window.defer(cx, move |window, cx| {
 603                                panel.update(cx, |this, cx| {
 604                                    this.activate_session_by_id(id, window, cx);
 605                                })
 606                            })
 607                        }
 608                    }
 609                    SessionEvent::Threads => {
 610                        let threads = this.session.update(cx, |this, cx| this.threads(cx));
 611                        this.select_current_thread(&threads, window, cx);
 612                    }
 613                    SessionEvent::CapabilitiesLoaded => {
 614                        let capabilities = this.capabilities(cx);
 615                        if !capabilities.supports_modules_request.unwrap_or(false) {
 616                            this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
 617                        }
 618                        if !capabilities
 619                            .supports_loaded_sources_request
 620                            .unwrap_or(false)
 621                        {
 622                            this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
 623                        }
 624                    }
 625                    SessionEvent::RunInTerminal { request, sender } => this
 626                        .handle_run_in_terminal(request, sender.clone(), window, cx)
 627                        .detach_and_log_err(cx),
 628
 629                    _ => {}
 630                }
 631                cx.notify()
 632            }),
 633            cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
 634                this.serialize_layout(window, cx);
 635            }),
 636        ];
 637
 638        let mut pane_close_subscriptions = HashMap::default();
 639        let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
 640            persistence::deserialize_pane_layout(
 641                serialized_layout.panes,
 642                dock_axis != serialized_layout.dock_axis,
 643                &workspace,
 644                &project,
 645                &stack_frame_list,
 646                &variable_list,
 647                &module_list,
 648                &console,
 649                &breakpoint_list,
 650                &loaded_source_list,
 651                &debug_terminal,
 652                &mut pane_close_subscriptions,
 653                window,
 654                cx,
 655            )
 656        }) {
 657            workspace::PaneGroup::with_root(root)
 658        } else {
 659            pane_close_subscriptions.clear();
 660
 661            let root = Self::default_pane_layout(
 662                project,
 663                &workspace,
 664                &stack_frame_list,
 665                &variable_list,
 666                &console,
 667                &breakpoint_list,
 668                &debug_terminal,
 669                dock_axis,
 670                &mut pane_close_subscriptions,
 671                window,
 672                cx,
 673            );
 674
 675            workspace::PaneGroup::with_root(root)
 676        };
 677
 678        Self {
 679            session,
 680            workspace,
 681            focus_handle,
 682            variable_list,
 683            _subscriptions,
 684            thread_id: None,
 685            _remote_id: None,
 686            stack_frame_list,
 687            session_id,
 688            panes,
 689            active_pane: None,
 690            module_list,
 691            console,
 692            breakpoint_list,
 693            loaded_sources_list: loaded_source_list,
 694            pane_close_subscriptions,
 695            debug_terminal,
 696            dock_axis,
 697            _schedule_serialize: None,
 698        }
 699    }
 700
 701    pub(crate) fn remove_pane_item(
 702        &mut self,
 703        item_kind: DebuggerPaneItem,
 704        window: &mut Window,
 705        cx: &mut Context<Self>,
 706    ) {
 707        if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
 708            Some(pane).zip(
 709                pane.read(cx)
 710                    .items()
 711                    .find(|item| {
 712                        item.act_as::<SubView>(cx)
 713                            .is_some_and(|view| view.read(cx).kind == item_kind)
 714                    })
 715                    .map(|item| item.item_id()),
 716            )
 717        }) {
 718            pane.update(cx, |pane, cx| {
 719                pane.remove_item(item_id, false, true, window, cx)
 720            })
 721        }
 722    }
 723
 724    pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
 725        self.panes.pane_at_pixel_position(position).is_some()
 726    }
 727
 728    pub(crate) fn resolve_scenario(
 729        &self,
 730        scenario: DebugScenario,
 731        task_context: TaskContext,
 732        buffer: Option<Entity<Buffer>>,
 733        worktree_id: Option<WorktreeId>,
 734        window: &Window,
 735        cx: &mut Context<Self>,
 736    ) -> Task<Result<DebugTaskDefinition>> {
 737        let Some(workspace) = self.workspace.upgrade() else {
 738            return Task::ready(Err(anyhow!("no workspace")));
 739        };
 740        let project = workspace.read(cx).project().clone();
 741        let dap_store = project.read(cx).dap_store().downgrade();
 742        let dap_registry = cx.global::<DapRegistry>().clone();
 743        let task_store = project.read(cx).task_store().downgrade();
 744        let weak_project = project.downgrade();
 745        let weak_workspace = workspace.downgrade();
 746        let is_local = project.read(cx).is_local();
 747        cx.spawn_in(window, async move |this, cx| {
 748            let DebugScenario {
 749                adapter,
 750                label,
 751                build,
 752                mut config,
 753                tcp_connection,
 754            } = scenario;
 755            Self::substitute_variables_in_config(&mut config, &task_context);
 756
 757            let request_type = dap_registry
 758                .adapter(&adapter)
 759                .ok_or_else(|| anyhow!("{}: is not a valid adapter name", &adapter))
 760                .and_then(|adapter| adapter.validate_config(&config));
 761
 762            let config_is_valid = request_type.is_ok();
 763
 764            let build_output = if let Some(build) = build {
 765                let (task, locator_name) = match build {
 766                    BuildTaskDefinition::Template {
 767                        task_template,
 768                        locator_name,
 769                    } => (task_template, locator_name),
 770                    BuildTaskDefinition::ByName(ref label) => {
 771                        let Some(task) = task_store.update(cx, |this, cx| {
 772                            this.task_inventory().and_then(|inventory| {
 773                                inventory.read(cx).task_template_by_label(
 774                                    buffer,
 775                                    worktree_id,
 776                                    &label,
 777                                    cx,
 778                                )
 779                            })
 780                        })?
 781                        else {
 782                            anyhow::bail!("Couldn't find task template for {:?}", build)
 783                        };
 784                        (task, None)
 785                    }
 786                };
 787                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
 788                    anyhow::bail!("Could not resolve task variables within a debug scenario");
 789                };
 790
 791                let locator_name = if let Some(locator_name) = locator_name {
 792                    debug_assert!(!config_is_valid);
 793                    Some(locator_name)
 794                } else if !config_is_valid {
 795                    dap_store
 796                        .update(cx, |this, cx| {
 797                            this.debug_scenario_for_build_task(
 798                                task.original_task().clone(),
 799                                adapter.clone().into(),
 800                                task.display_label().to_owned().into(),
 801                                cx,
 802                            )
 803                            .and_then(|scenario| {
 804                                match scenario.build {
 805                                    Some(BuildTaskDefinition::Template {
 806                                        locator_name, ..
 807                                    }) => locator_name,
 808                                    _ => None,
 809                                }
 810                            })
 811                        })
 812                        .ok()
 813                        .flatten()
 814                } else {
 815                    None
 816                };
 817
 818                let builder = ShellBuilder::new(is_local, &task.resolved.shell);
 819                let command_label = builder.command_label(&task.resolved.command_label);
 820                let (command, args) =
 821                    builder.build(task.resolved.command.clone(), &task.resolved.args);
 822
 823                let task_with_shell = SpawnInTerminal {
 824                    command_label,
 825                    command,
 826                    args,
 827                    ..task.resolved.clone()
 828                };
 829                let terminal = project
 830                    .update_in(cx, |project, window, cx| {
 831                        project.create_terminal(
 832                            TerminalKind::Task(task_with_shell.clone()),
 833                            window.window_handle(),
 834                            cx,
 835                        )
 836                    })?
 837                    .await?;
 838
 839                let terminal_view = cx.new_window_entity(|window, cx| {
 840                    TerminalView::new(
 841                        terminal.clone(),
 842                        weak_workspace,
 843                        None,
 844                        weak_project,
 845                        false,
 846                        window,
 847                        cx,
 848                    )
 849                })?;
 850
 851                this.update_in(cx, |this, window, cx| {
 852                    this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
 853                    this.debug_terminal.update(cx, |debug_terminal, cx| {
 854                        debug_terminal.terminal = Some(terminal_view);
 855                        cx.notify();
 856                    });
 857                })?;
 858
 859                let exit_status = terminal
 860                    .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
 861                    .await
 862                    .context("Failed to wait for completed task")?;
 863
 864                if !exit_status.success() {
 865                    anyhow::bail!("Build failed");
 866                }
 867                Some((task.resolved.clone(), locator_name))
 868            } else {
 869                None
 870            };
 871
 872            if config_is_valid {
 873                // Ok(DebugTaskDefinition {
 874                //     label,
 875                //     adapter: DebugAdapterName(adapter),
 876                //     config,
 877                //     tcp_connection,
 878                // })
 879            } else if let Some((task, locator_name)) = build_output {
 880                let locator_name =
 881                    locator_name.context("Could not find a valid locator for a build task")?;
 882                let request = dap_store
 883                    .update(cx, |this, cx| {
 884                        this.run_debug_locator(&locator_name, task, cx)
 885                    })?
 886                    .await?;
 887
 888                let zed_config = ZedDebugConfig {
 889                    label: label.clone(),
 890                    adapter: adapter.clone(),
 891                    request,
 892                    stop_on_entry: None,
 893                };
 894
 895                let scenario = dap_registry
 896                    .adapter(&adapter)
 897                    .ok_or_else(|| anyhow!("{}: is not a valid adapter name", &adapter))
 898                    .map(|adapter| adapter.config_from_zed_format(zed_config))??;
 899                config = scenario.config;
 900            } else {
 901                anyhow::bail!("No request or build provided");
 902            };
 903
 904            Ok(DebugTaskDefinition {
 905                label,
 906                adapter: DebugAdapterName(adapter),
 907                config,
 908                tcp_connection,
 909            })
 910        })
 911    }
 912
 913    fn handle_run_in_terminal(
 914        &self,
 915        request: &RunInTerminalRequestArguments,
 916        mut sender: mpsc::Sender<Result<u32>>,
 917        window: &mut Window,
 918        cx: &mut Context<Self>,
 919    ) -> Task<Result<()>> {
 920        let running = cx.entity();
 921        let Ok(project) = self
 922            .workspace
 923            .update(cx, |workspace, _| workspace.project().clone())
 924        else {
 925            return Task::ready(Err(anyhow!("no workspace")));
 926        };
 927        let session = self.session.read(cx);
 928
 929        let cwd = Some(&request.cwd)
 930            .filter(|cwd| cwd.len() > 0)
 931            .map(PathBuf::from)
 932            .or_else(|| session.binary().cwd.clone());
 933
 934        let mut args = request.args.clone();
 935
 936        // Handle special case for NodeJS debug adapter
 937        // If only the Node binary path is provided, we set the command to None
 938        // This prevents the NodeJS REPL from appearing, which is not the desired behavior
 939        // The expected usage is for users to provide their own Node command, e.g., `node test.js`
 940        // This allows the NodeJS debug client to attach correctly
 941        let command = if args.len() > 1 {
 942            Some(args.remove(0))
 943        } else {
 944            None
 945        };
 946
 947        let mut envs: HashMap<String, String> = Default::default();
 948        if let Some(Value::Object(env)) = &request.env {
 949            for (key, value) in env {
 950                let value_str = match (key.as_str(), value) {
 951                    (_, Value::String(value)) => value,
 952                    _ => continue,
 953                };
 954
 955                envs.insert(key.clone(), value_str.clone());
 956            }
 957        }
 958
 959        let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
 960        let kind = if let Some(command) = command {
 961            let title = request.title.clone().unwrap_or(command.clone());
 962            TerminalKind::Task(task::SpawnInTerminal {
 963                id: task::TaskId("debug".to_string()),
 964                full_label: title.clone(),
 965                label: title.clone(),
 966                command: command.clone(),
 967                args,
 968                command_label: title.clone(),
 969                cwd,
 970                env: envs,
 971                use_new_terminal: true,
 972                allow_concurrent_runs: true,
 973                reveal: task::RevealStrategy::NoFocus,
 974                reveal_target: task::RevealTarget::Dock,
 975                hide: task::HideStrategy::Never,
 976                shell,
 977                show_summary: false,
 978                show_command: false,
 979                show_rerun: false,
 980            })
 981        } else {
 982            TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
 983        };
 984
 985        let workspace = self.workspace.clone();
 986        let weak_project = project.downgrade();
 987
 988        let terminal_task = project.update(cx, |project, cx| {
 989            project.create_terminal(kind, window.window_handle(), cx)
 990        });
 991        let terminal_task = cx.spawn_in(window, async move |_, cx| {
 992            let terminal = terminal_task.await?;
 993
 994            let terminal_view = cx.new_window_entity(|window, cx| {
 995                TerminalView::new(
 996                    terminal.clone(),
 997                    workspace,
 998                    None,
 999                    weak_project,
1000                    false,
1001                    window,
1002                    cx,
1003                )
1004            })?;
1005
1006            running.update_in(cx, |running, window, cx| {
1007                running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
1008                running.debug_terminal.update(cx, |debug_terminal, cx| {
1009                    debug_terminal.terminal = Some(terminal_view);
1010                    cx.notify();
1011                });
1012            })?;
1013
1014            terminal.read_with(cx, |terminal, _| {
1015                terminal
1016                    .pty_info
1017                    .pid()
1018                    .map(|pid| pid.as_u32())
1019                    .context("Terminal was spawned but PID was not available")
1020            })?
1021        });
1022
1023        cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
1024    }
1025
1026    fn create_sub_view(
1027        &self,
1028        item_kind: DebuggerPaneItem,
1029        _pane: &Entity<Pane>,
1030        cx: &mut Context<Self>,
1031    ) -> Box<dyn ItemHandle> {
1032        match item_kind {
1033            DebuggerPaneItem::Console => {
1034                let weak_console = self.console.clone().downgrade();
1035
1036                Box::new(SubView::new(
1037                    self.console.focus_handle(cx),
1038                    self.console.clone().into(),
1039                    item_kind,
1040                    Some(Box::new(move |cx| {
1041                        weak_console
1042                            .read_with(cx, |console, cx| console.show_indicator(cx))
1043                            .unwrap_or_default()
1044                    })),
1045                    cx,
1046                ))
1047            }
1048            DebuggerPaneItem::Variables => Box::new(SubView::new(
1049                self.variable_list.focus_handle(cx),
1050                self.variable_list.clone().into(),
1051                item_kind,
1052                None,
1053                cx,
1054            )),
1055            DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
1056                self.breakpoint_list.focus_handle(cx),
1057                self.breakpoint_list.clone().into(),
1058                item_kind,
1059                None,
1060                cx,
1061            )),
1062            DebuggerPaneItem::Frames => Box::new(SubView::new(
1063                self.stack_frame_list.focus_handle(cx),
1064                self.stack_frame_list.clone().into(),
1065                item_kind,
1066                None,
1067                cx,
1068            )),
1069            DebuggerPaneItem::Modules => Box::new(SubView::new(
1070                self.module_list.focus_handle(cx),
1071                self.module_list.clone().into(),
1072                item_kind,
1073                None,
1074                cx,
1075            )),
1076            DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
1077                self.loaded_sources_list.focus_handle(cx),
1078                self.loaded_sources_list.clone().into(),
1079                item_kind,
1080                None,
1081                cx,
1082            )),
1083            DebuggerPaneItem::Terminal => Box::new(SubView::new(
1084                self.debug_terminal.focus_handle(cx),
1085                self.debug_terminal.clone().into(),
1086                item_kind,
1087                None,
1088                cx,
1089            )),
1090        }
1091    }
1092
1093    pub(crate) fn ensure_pane_item(
1094        &mut self,
1095        item_kind: DebuggerPaneItem,
1096        window: &mut Window,
1097        cx: &mut Context<Self>,
1098    ) {
1099        if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
1100            return;
1101        };
1102        let pane = self.panes.last_pane();
1103        let sub_view = self.create_sub_view(item_kind, &pane, cx);
1104
1105        pane.update(cx, |pane, cx| {
1106            pane.add_item_inner(sub_view, false, false, false, None, window, cx);
1107        })
1108    }
1109
1110    pub(crate) fn add_pane_item(
1111        &mut self,
1112        item_kind: DebuggerPaneItem,
1113        position: Point<Pixels>,
1114        window: &mut Window,
1115        cx: &mut Context<Self>,
1116    ) {
1117        debug_assert!(
1118            item_kind.is_supported(self.session.read(cx).capabilities()),
1119            "We should only allow adding supported item kinds"
1120        );
1121
1122        if let Some(pane) = self.panes.pane_at_pixel_position(position) {
1123            let sub_view = self.create_sub_view(item_kind, pane, cx);
1124
1125            pane.update(cx, |pane, cx| {
1126                pane.add_item(sub_view, false, false, None, window, cx);
1127            })
1128        }
1129    }
1130
1131    pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
1132        let caps = self.session.read(cx).capabilities();
1133        let mut pane_item_status = IndexMap::from_iter(
1134            DebuggerPaneItem::all()
1135                .iter()
1136                .filter(|kind| kind.is_supported(&caps))
1137                .map(|kind| (*kind, false)),
1138        );
1139        self.panes.panes().iter().for_each(|pane| {
1140            pane.read(cx)
1141                .items()
1142                .filter_map(|item| item.act_as::<SubView>(cx))
1143                .for_each(|view| {
1144                    pane_item_status.insert(view.read(cx).kind, true);
1145                });
1146        });
1147
1148        pane_item_status
1149    }
1150
1151    pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1152        if self._schedule_serialize.is_none() {
1153            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
1154                cx.background_executor()
1155                    .timer(Duration::from_millis(100))
1156                    .await;
1157
1158                let Some((adapter_name, pane_layout)) = this
1159                    .read_with(cx, |this, cx| {
1160                        let adapter_name = this.session.read(cx).adapter();
1161                        (
1162                            adapter_name,
1163                            persistence::build_serialized_layout(
1164                                &this.panes.root,
1165                                this.dock_axis,
1166                                cx,
1167                            ),
1168                        )
1169                    })
1170                    .ok()
1171                else {
1172                    return;
1173                };
1174
1175                persistence::serialize_pane_layout(adapter_name, pane_layout)
1176                    .await
1177                    .log_err();
1178
1179                this.update(cx, |this, _| {
1180                    this._schedule_serialize.take();
1181                })
1182                .ok();
1183            }));
1184        }
1185    }
1186
1187    pub(crate) fn handle_pane_event(
1188        this: &mut RunningState,
1189        source_pane: &Entity<Pane>,
1190        event: &Event,
1191        window: &mut Window,
1192        cx: &mut Context<RunningState>,
1193    ) {
1194        this.serialize_layout(window, cx);
1195        match event {
1196            Event::Remove { .. } => {
1197                let _did_find_pane = this.panes.remove(&source_pane).is_ok();
1198                debug_assert!(_did_find_pane);
1199                cx.notify();
1200            }
1201            Event::Focus => {
1202                this.active_pane = Some(source_pane.clone());
1203            }
1204            Event::ZoomIn => {
1205                source_pane.update(cx, |pane, cx| {
1206                    pane.set_zoomed(true, cx);
1207                });
1208                cx.notify();
1209            }
1210            Event::ZoomOut => {
1211                source_pane.update(cx, |pane, cx| {
1212                    pane.set_zoomed(false, cx);
1213                });
1214                cx.notify();
1215            }
1216            _ => {}
1217        }
1218    }
1219
1220    pub(crate) fn activate_pane_in_direction(
1221        &mut self,
1222        direction: SplitDirection,
1223        window: &mut Window,
1224        cx: &mut Context<Self>,
1225    ) {
1226        if let Some(pane) = self
1227            .active_pane
1228            .as_ref()
1229            .and_then(|pane| self.panes.find_pane_in_direction(pane, direction, cx))
1230        {
1231            pane.update(cx, |pane, cx| {
1232                pane.focus_active_item(window, cx);
1233            })
1234        } else {
1235            self.workspace
1236                .update(cx, |workspace, cx| {
1237                    workspace.activate_pane_in_direction(direction, window, cx)
1238                })
1239                .ok();
1240        }
1241    }
1242
1243    pub(crate) fn go_to_selected_stack_frame(&self, window: &mut Window, cx: &mut Context<Self>) {
1244        if self.thread_id.is_some() {
1245            self.stack_frame_list
1246                .update(cx, |list, cx| {
1247                    let Some(stack_frame_id) = list.opened_stack_frame_id() else {
1248                        return Task::ready(Ok(()));
1249                    };
1250                    list.go_to_stack_frame(stack_frame_id, window, cx)
1251                })
1252                .detach();
1253        }
1254    }
1255
1256    pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
1257        self.variable_list.read(cx).has_open_context_menu()
1258    }
1259
1260    pub fn session(&self) -> &Entity<Session> {
1261        &self.session
1262    }
1263
1264    pub fn session_id(&self) -> SessionId {
1265        self.session_id
1266    }
1267
1268    pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
1269        self.stack_frame_list.read(cx).opened_stack_frame_id()
1270    }
1271
1272    pub(crate) fn stack_frame_list(&self) -> &Entity<StackFrameList> {
1273        &self.stack_frame_list
1274    }
1275
1276    #[cfg(test)]
1277    pub fn console(&self) -> &Entity<Console> {
1278        &self.console
1279    }
1280
1281    #[cfg(test)]
1282    pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
1283        &self.module_list
1284    }
1285
1286    pub(crate) fn activate_item(&self, item: DebuggerPaneItem, window: &mut Window, cx: &mut App) {
1287        let (variable_list_position, pane) = self
1288            .panes
1289            .panes()
1290            .into_iter()
1291            .find_map(|pane| {
1292                pane.read(cx)
1293                    .items_of_type::<SubView>()
1294                    .position(|view| view.read(cx).view_kind() == item)
1295                    .map(|view| (view, pane))
1296            })
1297            .unwrap();
1298        pane.update(cx, |this, cx| {
1299            this.activate_item(variable_list_position, true, true, window, cx);
1300        })
1301    }
1302
1303    #[cfg(test)]
1304    pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
1305        &self.variable_list
1306    }
1307
1308    #[cfg(test)]
1309    pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
1310        persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
1311    }
1312
1313    pub fn capabilities(&self, cx: &App) -> Capabilities {
1314        self.session().read(cx).capabilities().clone()
1315    }
1316
1317    pub fn select_current_thread(
1318        &mut self,
1319        threads: &Vec<(Thread, ThreadStatus)>,
1320        window: &mut Window,
1321        cx: &mut Context<Self>,
1322    ) {
1323        let selected_thread = self
1324            .thread_id
1325            .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
1326            .or_else(|| threads.first());
1327
1328        let Some((selected_thread, _)) = selected_thread else {
1329            return;
1330        };
1331
1332        if Some(ThreadId(selected_thread.id)) != self.thread_id {
1333            self.select_thread(ThreadId(selected_thread.id), window, cx);
1334        }
1335    }
1336
1337    pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
1338        self.thread_id
1339    }
1340
1341    pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
1342        self.thread_id
1343            .map(|id| self.session().read(cx).thread_status(id))
1344    }
1345
1346    pub(crate) fn select_thread(
1347        &mut self,
1348        thread_id: ThreadId,
1349        window: &mut Window,
1350        cx: &mut Context<Self>,
1351    ) {
1352        if self.thread_id.is_some_and(|id| id == thread_id) {
1353            return;
1354        }
1355
1356        self.thread_id = Some(thread_id);
1357
1358        self.stack_frame_list
1359            .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
1360    }
1361
1362    pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
1363        let Some(thread_id) = self.thread_id else {
1364            return;
1365        };
1366
1367        self.session().update(cx, |state, cx| {
1368            state.continue_thread(thread_id, cx);
1369        });
1370    }
1371
1372    pub fn step_over(&mut self, cx: &mut Context<Self>) {
1373        let Some(thread_id) = self.thread_id else {
1374            return;
1375        };
1376
1377        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1378
1379        self.session().update(cx, |state, cx| {
1380            state.step_over(thread_id, granularity, cx);
1381        });
1382    }
1383
1384    pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
1385        let Some(thread_id) = self.thread_id else {
1386            return;
1387        };
1388
1389        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1390
1391        self.session().update(cx, |state, cx| {
1392            state.step_in(thread_id, granularity, cx);
1393        });
1394    }
1395
1396    pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
1397        let Some(thread_id) = self.thread_id else {
1398            return;
1399        };
1400
1401        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1402
1403        self.session().update(cx, |state, cx| {
1404            state.step_out(thread_id, granularity, cx);
1405        });
1406    }
1407
1408    pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
1409        let Some(thread_id) = self.thread_id else {
1410            return;
1411        };
1412
1413        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1414
1415        self.session().update(cx, |state, cx| {
1416            state.step_back(thread_id, granularity, cx);
1417        });
1418    }
1419
1420    pub fn restart_session(&self, cx: &mut Context<Self>) {
1421        self.session().update(cx, |state, cx| {
1422            state.restart(None, cx);
1423        });
1424    }
1425
1426    pub fn pause_thread(&self, cx: &mut Context<Self>) {
1427        let Some(thread_id) = self.thread_id else {
1428            return;
1429        };
1430
1431        self.session().update(cx, |state, cx| {
1432            state.pause_thread(thread_id, cx);
1433        });
1434    }
1435
1436    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
1437        self.workspace
1438            .update(cx, |workspace, cx| {
1439                workspace
1440                    .project()
1441                    .read(cx)
1442                    .breakpoint_store()
1443                    .update(cx, |store, cx| {
1444                        store.remove_active_position(Some(self.session_id), cx)
1445                    })
1446            })
1447            .log_err();
1448
1449        self.session.update(cx, |session, cx| {
1450            session.shutdown(cx).detach();
1451        })
1452    }
1453
1454    pub fn stop_thread(&self, cx: &mut Context<Self>) {
1455        let Some(thread_id) = self.thread_id else {
1456            return;
1457        };
1458
1459        self.workspace
1460            .update(cx, |workspace, cx| {
1461                workspace
1462                    .project()
1463                    .read(cx)
1464                    .breakpoint_store()
1465                    .update(cx, |store, cx| {
1466                        store.remove_active_position(Some(self.session_id), cx)
1467                    })
1468            })
1469            .log_err();
1470
1471        self.session().update(cx, |state, cx| {
1472            state.terminate_threads(Some(vec![thread_id; 1]), cx);
1473        });
1474    }
1475
1476    pub fn detach_client(&self, cx: &mut Context<Self>) {
1477        self.session().update(cx, |state, cx| {
1478            state.disconnect_client(cx);
1479        });
1480    }
1481
1482    pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1483        self.session.update(cx, |session, cx| {
1484            session.toggle_ignore_breakpoints(cx).detach();
1485        });
1486    }
1487
1488    fn default_pane_layout(
1489        project: Entity<Project>,
1490        workspace: &WeakEntity<Workspace>,
1491        stack_frame_list: &Entity<StackFrameList>,
1492        variable_list: &Entity<VariableList>,
1493        console: &Entity<Console>,
1494        breakpoints: &Entity<BreakpointList>,
1495        debug_terminal: &Entity<DebugTerminal>,
1496        dock_axis: Axis,
1497        subscriptions: &mut HashMap<EntityId, Subscription>,
1498        window: &mut Window,
1499        cx: &mut Context<'_, RunningState>,
1500    ) -> Member {
1501        let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1502        leftmost_pane.update(cx, |this, cx| {
1503            this.add_item(
1504                Box::new(SubView::new(
1505                    this.focus_handle(cx),
1506                    stack_frame_list.clone().into(),
1507                    DebuggerPaneItem::Frames,
1508                    None,
1509                    cx,
1510                )),
1511                true,
1512                false,
1513                None,
1514                window,
1515                cx,
1516            );
1517            this.add_item(
1518                Box::new(SubView::new(
1519                    breakpoints.focus_handle(cx),
1520                    breakpoints.clone().into(),
1521                    DebuggerPaneItem::BreakpointList,
1522                    None,
1523                    cx,
1524                )),
1525                true,
1526                false,
1527                None,
1528                window,
1529                cx,
1530            );
1531            this.activate_item(0, false, false, window, cx);
1532        });
1533        let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1534
1535        center_pane.update(cx, |this, cx| {
1536            let weak_console = console.downgrade();
1537            this.add_item(
1538                Box::new(SubView::new(
1539                    console.focus_handle(cx),
1540                    console.clone().into(),
1541                    DebuggerPaneItem::Console,
1542                    Some(Box::new(move |cx| {
1543                        weak_console
1544                            .read_with(cx, |console, cx| console.show_indicator(cx))
1545                            .unwrap_or_default()
1546                    })),
1547                    cx,
1548                )),
1549                true,
1550                false,
1551                None,
1552                window,
1553                cx,
1554            );
1555
1556            this.add_item(
1557                Box::new(SubView::new(
1558                    variable_list.focus_handle(cx),
1559                    variable_list.clone().into(),
1560                    DebuggerPaneItem::Variables,
1561                    None,
1562                    cx,
1563                )),
1564                true,
1565                false,
1566                None,
1567                window,
1568                cx,
1569            );
1570            this.activate_item(0, false, false, window, cx);
1571        });
1572
1573        let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1574        rightmost_pane.update(cx, |this, cx| {
1575            this.add_item(
1576                Box::new(SubView::new(
1577                    debug_terminal.focus_handle(cx),
1578                    debug_terminal.clone().into(),
1579                    DebuggerPaneItem::Terminal,
1580                    None,
1581                    cx,
1582                )),
1583                false,
1584                false,
1585                None,
1586                window,
1587                cx,
1588            );
1589        });
1590
1591        subscriptions.extend(
1592            [&leftmost_pane, &center_pane, &rightmost_pane]
1593                .into_iter()
1594                .map(|entity| {
1595                    (
1596                        entity.entity_id(),
1597                        cx.subscribe_in(entity, window, Self::handle_pane_event),
1598                    )
1599                }),
1600        );
1601
1602        let group_root = workspace::PaneAxis::new(
1603            dock_axis.invert(),
1604            [leftmost_pane, center_pane, rightmost_pane]
1605                .into_iter()
1606                .map(workspace::Member::Pane)
1607                .collect(),
1608        );
1609
1610        Member::Axis(group_root)
1611    }
1612
1613    pub(crate) fn invert_axies(&mut self) {
1614        self.dock_axis = self.dock_axis.invert();
1615        self.panes.invert_axies();
1616    }
1617}
1618
1619impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1620
1621impl Focusable for RunningState {
1622    fn focus_handle(&self, _: &App) -> FocusHandle {
1623        self.focus_handle.clone()
1624    }
1625}