Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:37:08.458Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

running.rs

2124 lines · 73.6 KB · rust
1pub(crate) mod breakpoint_list;
2pub(crate) mod console;
3pub(crate) mod loaded_source_list;
4pub(crate) mod memory_view;
5pub(crate) mod module_list;
6pub mod stack_frame_list;
7pub mod variable_list;
8use std::{
9    any::Any,
10    path::PathBuf,
11    sync::{Arc, LazyLock},
12    time::Duration,
13};
14
15use crate::{
16    ToggleExpandItem,
17    attach_modal::{AttachModal, ModalIntent},
18    new_process_modal::resolve_path,
19    persistence::{self, DebuggerPaneItem, SerializedLayout},
20    session::running::memory_view::MemoryView,
21};
22
23use anyhow::{Context as _, Result, anyhow, bail};
24use breakpoint_list::BreakpointList;
25use collections::{HashMap, IndexMap};
26use console::Console;
27use dap::{
28    Capabilities, DapRegistry, RunInTerminalRequestArguments, Thread,
29    adapters::{DebugAdapterName, DebugTaskDefinition},
30    client::SessionId,
31    debugger_settings::DebuggerSettings,
32};
33use futures::{SinkExt, channel::mpsc};
34use gpui::{
35    Action as _, AnyView, AppContext, Axis, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
36    NoAction, Pixels, Point, Subscription, Task, TaskExt, WeakEntity,
37};
38use language::Buffer;
39use loaded_source_list::LoadedSourceList;
40use module_list::ModuleList;
41use project::{
42    DebugScenarioContext, Project, WorktreeId,
43    debugger::session::{self, Session, SessionEvent, SessionStateEvent, ThreadId, ThreadStatus},
44};
45use rpc::proto::ViewId;
46use serde_json::Value;
47use settings::Settings;
48use stack_frame_list::StackFrameList;
49use task::{
50    BuildTaskDefinition, DebugScenario, SharedTaskContext, Shell, ShellBuilder, SpawnInTerminal,
51    TaskContext, ZedDebugConfig, substitute_variables_in_str,
52};
53use terminal_view::TerminalView;
54use ui::{
55    FluentBuilder, IntoElement, Render, StatefulInteractiveElement, Tab, Tooltip, VisibleOnHover,
56    VisualContext, prelude::*,
57};
58use util::ResultExt;
59use variable_list::VariableList;
60use workspace::{
61    ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, SplitDirection,
62    Workspace, item::TabContentParams, move_item, pane::Event,
63};
64
65static PROCESS_ID_PLACEHOLDER: LazyLock<String> =
66    LazyLock::new(|| task::VariableName::PickProcessId.template_value());
67
68pub struct RunningState {
69    session: Entity<Session>,
70    thread_id: Option<ThreadId>,
71    focus_handle: FocusHandle,
72    _remote_id: Option<ViewId>,
73    workspace: WeakEntity<Workspace>,
74    project: WeakEntity<Project>,
75    session_id: SessionId,
76    variable_list: Entity<variable_list::VariableList>,
77    _subscriptions: Vec<Subscription>,
78    stack_frame_list: Entity<stack_frame_list::StackFrameList>,
79    loaded_sources_list: Entity<LoadedSourceList>,
80    pub debug_terminal: Entity<DebugTerminal>,
81    module_list: Entity<module_list::ModuleList>,
82    console: Entity<Console>,
83    breakpoint_list: Entity<BreakpointList>,
84    panes: PaneGroup,
85    active_pane: Entity<Pane>,
86    pane_close_subscriptions: HashMap<EntityId, Subscription>,
87    dock_axis: Axis,
88    _schedule_serialize: Option<Task<()>>,
89    pub(crate) scenario: Option<DebugScenario>,
90    pub(crate) scenario_context: Option<DebugScenarioContext>,
91    memory_view: Entity<MemoryView>,
92}
93
94impl RunningState {
95    pub(crate) fn thread_id(&self) -> Option<ThreadId> {
96        self.thread_id
97    }
98
99    pub(crate) fn active_pane(&self) -> &Entity<Pane> {
100        &self.active_pane
101    }
102}
103
104impl Render for RunningState {
105    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
106        let zoomed_pane = self
107            .panes
108            .panes()
109            .into_iter()
110            .find(|pane| pane.read(cx).is_zoomed());
111
112        let active = self.panes.panes().into_iter().next();
113        let pane = if let Some(zoomed_pane) = zoomed_pane {
114            zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element())
115        } else if let Some(active) = active {
116            self.panes
117                .render(
118                    None,
119                    None,
120                    &ActivePaneDecorator::new(active, &self.workspace),
121                    window,
122                    cx,
123                )
124                .into_any_element()
125        } else {
126            div().into_any_element()
127        };
128        let thread_status = self
129            .thread_id
130            .map(|thread_id| self.session.read(cx).thread_status(thread_id))
131            .unwrap_or(ThreadStatus::Exited);
132
133        self.variable_list.update(cx, |this, cx| {
134            this.disabled(thread_status != ThreadStatus::Stopped, cx);
135        });
136        v_flex()
137            .size_full()
138            .key_context("DebugSessionItem")
139            .track_focus(&self.focus_handle(cx))
140            .child(h_flex().flex_1().child(pane))
141    }
142}
143
144pub(crate) struct SubView {
145    inner: AnyView,
146    item_focus_handle: FocusHandle,
147    kind: DebuggerPaneItem,
148    running_state: WeakEntity<RunningState>,
149    host_pane: WeakEntity<Pane>,
150    show_indicator: Box<dyn Fn(&App) -> bool>,
151    actions: Option<Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>>,
152    hovered: bool,
153}
154
155impl SubView {
156    pub(crate) fn new(
157        item_focus_handle: FocusHandle,
158        view: AnyView,
159        kind: DebuggerPaneItem,
160        running_state: WeakEntity<RunningState>,
161        host_pane: WeakEntity<Pane>,
162        cx: &mut App,
163    ) -> Entity<Self> {
164        cx.new(|_| Self {
165            kind,
166            inner: view,
167            item_focus_handle,
168            running_state,
169            host_pane,
170            show_indicator: Box::new(|_| false),
171            actions: None,
172            hovered: false,
173        })
174    }
175
176    pub(crate) fn stack_frame_list(
177        stack_frame_list: Entity<StackFrameList>,
178        running_state: WeakEntity<RunningState>,
179        host_pane: WeakEntity<Pane>,
180        cx: &mut App,
181    ) -> Entity<Self> {
182        let weak_list = stack_frame_list.downgrade();
183        let this = Self::new(
184            stack_frame_list.focus_handle(cx),
185            stack_frame_list.into(),
186            DebuggerPaneItem::Frames,
187            running_state,
188            host_pane,
189            cx,
190        );
191
192        this.update(cx, |this, _| {
193            this.with_actions(Box::new(move |_, cx| {
194                weak_list
195                    .update(cx, |this, _| this.render_control_strip())
196                    .unwrap_or_else(|_| div().into_any_element())
197            }));
198        });
199
200        this
201    }
202
203    pub(crate) fn console(
204        console: Entity<Console>,
205        running_state: WeakEntity<RunningState>,
206        host_pane: WeakEntity<Pane>,
207        cx: &mut App,
208    ) -> Entity<Self> {
209        let weak_console = console.downgrade();
210        let this = Self::new(
211            console.focus_handle(cx),
212            console.into(),
213            DebuggerPaneItem::Console,
214            running_state,
215            host_pane,
216            cx,
217        );
218        this.update(cx, |this, _| {
219            this.with_indicator(Box::new(move |cx| {
220                weak_console
221                    .read_with(cx, |console, cx| console.show_indicator(cx))
222                    .unwrap_or_default()
223            }))
224        });
225        this
226    }
227
228    pub(crate) fn breakpoint_list(
229        list: Entity<BreakpointList>,
230        running_state: WeakEntity<RunningState>,
231        host_pane: WeakEntity<Pane>,
232        cx: &mut App,
233    ) -> Entity<Self> {
234        let weak_list = list.downgrade();
235        let focus_handle = list.focus_handle(cx);
236        let this = Self::new(
237            focus_handle,
238            list.into(),
239            DebuggerPaneItem::BreakpointList,
240            running_state,
241            host_pane,
242            cx,
243        );
244
245        this.update(cx, |this, _| {
246            this.with_actions(Box::new(move |_, cx| {
247                weak_list
248                    .update(cx, |this, _| this.render_control_strip())
249                    .unwrap_or_else(|_| div().into_any_element())
250            }));
251        });
252        this
253    }
254
255    pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
256        self.kind
257    }
258    pub(crate) fn with_indicator(&mut self, indicator: Box<dyn Fn(&App) -> bool>) {
259        self.show_indicator = indicator;
260    }
261    pub(crate) fn with_actions(
262        &mut self,
263        actions: Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>,
264    ) {
265        self.actions = Some(actions);
266    }
267
268    fn set_host_pane(&mut self, host_pane: WeakEntity<Pane>) {
269        self.host_pane = host_pane;
270    }
271}
272impl Focusable for SubView {
273    fn focus_handle(&self, _: &App) -> FocusHandle {
274        self.item_focus_handle.clone()
275    }
276}
277impl EventEmitter<()> for SubView {}
278impl Item for SubView {
279    type Event = ();
280
281    /// This is used to serialize debugger pane layouts
282    /// A SharedString gets converted to a enum and back during serialization/deserialization.
283    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
284        self.kind.to_shared_string()
285    }
286
287    fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> {
288        Some(self.kind.tab_tooltip())
289    }
290
291    fn tab_content(
292        &self,
293        params: workspace::item::TabContentParams,
294        _: &Window,
295        cx: &App,
296    ) -> AnyElement {
297        let label = Label::new(self.kind.to_shared_string())
298            .size(ui::LabelSize::Small)
299            .color(params.text_color())
300            .line_height_style(ui::LineHeightStyle::UiLabel);
301
302        if !params.selected && self.show_indicator.as_ref()(cx) {
303            return h_flex()
304                .justify_between()
305                .child(ui::Indicator::dot())
306                .gap_2()
307                .child(label)
308                .into_any_element();
309        }
310
311        label.into_any_element()
312    }
313
314    fn handle_drop(
315        &self,
316        active_pane: &Pane,
317        dropped: &dyn Any,
318        window: &mut Window,
319        cx: &mut App,
320    ) -> bool {
321        let Some(tab) = dropped.downcast_ref::<DraggedTab>() else {
322            return true;
323        };
324        let Some(this_pane) = self.host_pane.upgrade() else {
325            return true;
326        };
327        if tab.item.downcast::<SubView>().is_none() {
328            return true;
329        }
330        let Some(split_direction) = active_pane.drag_split_direction() else {
331            return false;
332        };
333
334        let source = tab.pane.clone();
335        let item_id_to_move = tab.item.item_id();
336        let weak_running = self.running_state.clone();
337
338        // Source pane may be the one currently updated, so defer the move.
339        window.defer(cx, move |window, cx| {
340            let new_pane = weak_running.update(cx, |running, cx| {
341                let Some(project) = running.project.upgrade() else {
342                    return Err(anyhow!("Debugger project has been dropped"));
343                };
344
345                let new_pane = new_debugger_pane(running.workspace.clone(), project, window, cx);
346                let _previous_subscription = running.pane_close_subscriptions.insert(
347                    new_pane.entity_id(),
348                    cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
349                );
350                debug_assert!(_previous_subscription.is_none());
351                running
352                    .panes
353                    .split(&this_pane, &new_pane, split_direction, cx);
354                anyhow::Ok(new_pane)
355            });
356
357            match new_pane.and_then(|result| result) {
358                Ok(new_pane) => {
359                    move_item(
360                        &source,
361                        &new_pane,
362                        item_id_to_move,
363                        new_pane.read(cx).active_item_index(),
364                        true,
365                        window,
366                        cx,
367                    );
368                }
369                Err(err) => {
370                    log::error!("{err:?}");
371                }
372            }
373        });
374
375        true
376    }
377}
378
379impl Render for SubView {
380    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
381        v_flex()
382            .id(format!(
383                "subview-container-{}",
384                self.kind.to_shared_string()
385            ))
386            .size_full()
387            .border_1()
388            .when(self.item_focus_handle.contains_focused(window, cx), |el| {
389                el.border_color(cx.theme().colors().pane_focused_border)
390            })
391            .child(self.inner.clone())
392            .on_hover(cx.listener(|this, hovered, _, cx| {
393                this.hovered = *hovered;
394                cx.notify();
395            }))
396    }
397}
398
399struct DraggedTabPreview {
400    label: SharedString,
401}
402
403impl Render for DraggedTabPreview {
404    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
405        let ui_font = theme_settings::ThemeSettings::get_global(cx)
406            .ui_font
407            .clone();
408        let colors = cx.theme().colors();
409
410        h_flex()
411            .font(ui_font)
412            .h_6()
413            .px_1()
414            .rounded_sm()
415            .shadow_md()
416            .border_1()
417            .border_color(colors.border)
418            .bg(colors.elevated_surface_background)
419            .child(Label::new(self.label.clone()).size(LabelSize::Small))
420    }
421}
422
423fn render_debugger_tab(
424    ix: usize,
425    item: &dyn ItemHandle,
426    selected: bool,
427    deemphasized: bool,
428    window: &mut Window,
429    cx: &mut Context<Pane>,
430) -> impl IntoElement + use<> {
431    let item_ = item.boxed_clone();
432    let colors = cx.theme().colors();
433
434    div()
435        .border_l_2()
436        .border_color(gpui::transparent_black())
437        .drag_over::<DraggedTab>(|wrapper, _, _, cx| wrapper.border_color(cx.theme().colors().text))
438        .child(
439            div()
440                .cursor_pointer()
441                .id(format!("debugger_tab_{}", item.item_id().as_u64()))
442                .p_1()
443                .rounded_sm()
444                .map(|s| {
445                    if selected {
446                        s.bg(colors.text_accent.opacity(0.08))
447                            .hover(|s| s.bg(colors.text_accent.opacity(0.25)))
448                            .text_color(colors.text_accent)
449                    } else {
450                        s.hover(|s| s.bg(colors.element_hover))
451                    }
452                })
453                .when(deemphasized, |s| s.opacity(0.8))
454                .child(item.tab_content(
455                    TabContentParams {
456                        selected,
457                        deemphasized,
458                        ..Default::default()
459                    },
460                    window,
461                    cx,
462                ))
463                .when_some(item.tab_tooltip_text(cx), |this, tooltip| {
464                    this.tooltip(Tooltip::text(tooltip))
465                })
466                .on_click(cx.listener(move |this, _, window, cx| {
467                    let index = this.index_for_item(&*item_);
468                    if let Some(index) = index {
469                        this.activate_item(index, true, true, window, cx);
470                    }
471                }))
472                .on_drop(
473                    cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| {
474                        if dragged_tab.item.downcast::<SubView>().is_none() {
475                            return;
476                        }
477                        this.drag_split_direction = None;
478                        this.handle_tab_drop(dragged_tab, ix, false, window, cx)
479                    }),
480                )
481                .on_drag(
482                    DraggedTab {
483                        item: item.boxed_clone(),
484                        pane: cx.entity(),
485                        detail: 0,
486                        is_active: selected,
487                        ix,
488                    },
489                    |tab, _, _, cx| {
490                        let label = tab.item.tab_content_text(0, cx);
491                        cx.new(|_| DraggedTabPreview { label })
492                    },
493                ),
494        )
495}
496
497fn render_debugger_tab_bar(
498    pane: &mut Pane,
499    focus_handle: &FocusHandle,
500    window: &mut Window,
501    cx: &mut Context<Pane>,
502) -> gpui::AnyElement {
503    let active_pane_item = pane.active_item();
504    let pane_group_id: SharedString = format!("pane-zoom-button-hover-{}", cx.entity_id()).into();
505    let as_subview = active_pane_item
506        .as_ref()
507        .and_then(|item| item.downcast::<SubView>());
508
509    let is_hovered = as_subview
510        .as_ref()
511        .is_some_and(|item| item.read(cx).hovered);
512    let deemphasized = !pane.has_focus(window, cx);
513
514    let tabs = pane
515        .items()
516        .enumerate()
517        .map(|(ix, item)| {
518            let selected = active_pane_item
519                .as_ref()
520                .is_some_and(|active| active.item_id() == item.item_id());
521            render_debugger_tab(ix, item.as_ref(), selected, deemphasized, window, cx)
522        })
523        .collect::<Vec<_>>();
524
525    h_flex()
526        .track_focus(focus_handle)
527        .group(pane_group_id.clone())
528        .on_action(|_: &menu::Cancel, window, cx| {
529            if cx.stop_active_drag(window) {
530            } else {
531                cx.propagate();
532            }
533        })
534        .pl_1p5()
535        .pr_1()
536        .justify_between()
537        .border_b_1()
538        .border_color(cx.theme().colors().border)
539        .bg(cx.theme().colors().tab_bar_background)
540        .child(
541            h_flex()
542                .w_full()
543                .gap_1()
544                .h(Tab::container_height(cx))
545                .children(tabs)
546                .on_drop(
547                    cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| {
548                        if dragged_tab.item.downcast::<SubView>().is_none() {
549                            return;
550                        }
551                        this.drag_split_direction = None;
552                        this.handle_tab_drop(dragged_tab, this.items_len(), false, window, cx)
553                    }),
554                )
555                .child(
556                    div()
557                        .flex_1()
558                        .h_6()
559                        .border_l_2()
560                        .border_color(gpui::transparent_black())
561                        .drag_over::<DraggedTab>(|spacer, _, _, cx| {
562                            spacer.border_color(cx.theme().colors().text)
563                        }),
564                ),
565        )
566        .child({
567            let zoomed = pane.is_zoomed();
568
569            h_flex()
570                .visible_on_hover(pane_group_id)
571                .when(is_hovered, |this| this.visible())
572                .when_some(as_subview.as_ref(), |this, subview| {
573                    subview.update(cx, |view, cx| {
574                        let Some(additional_actions) = view.actions.as_mut() else {
575                            return this;
576                        };
577                        this.child(additional_actions(window, cx))
578                    })
579                })
580                .child(
581                    IconButton::new(
582                        format!("debug-toggle-zoom-{}", cx.entity_id()),
583                        if zoomed {
584                            IconName::Minimize
585                        } else {
586                            IconName::Maximize
587                        },
588                    )
589                    .icon_size(IconSize::Small)
590                    .on_click(cx.listener(move |pane, _, _, cx| {
591                        let is_zoomed = pane.is_zoomed();
592                        pane.set_zoomed(!is_zoomed, cx);
593                        cx.notify();
594                    }))
595                    .tooltip({
596                        let focus_handle = focus_handle.clone();
597                        move |_window, cx| {
598                            let zoomed_text = if zoomed { "Minimize" } else { "Expand" };
599                            Tooltip::for_action_in(
600                                zoomed_text,
601                                &ToggleExpandItem,
602                                &focus_handle,
603                                cx,
604                            )
605                        }
606                    }),
607                )
608        })
609        .into_any_element()
610}
611
612pub(crate) fn new_debugger_pane(
613    workspace: WeakEntity<Workspace>,
614    project: Entity<Project>,
615    window: &mut Window,
616    cx: &mut Context<RunningState>,
617) -> Entity<Pane> {
618    let weak_running = cx.weak_entity();
619
620    cx.new(move |cx| {
621        let can_drop_predicate: Arc<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool> =
622            Arc::new(|any, _window, _cx| {
623                any.downcast_ref::<DraggedTab>()
624                    .is_some_and(|dragged_tab| dragged_tab.item.downcast::<SubView>().is_some())
625            });
626        let mut pane = Pane::new(
627            workspace.clone(),
628            project.clone(),
629            Default::default(),
630            Some(can_drop_predicate),
631            NoAction.boxed_clone(),
632            true,
633            window,
634            cx,
635        );
636        let focus_handle = pane.focus_handle(cx);
637        pane.set_can_split(Some(Arc::new({
638            let weak_running = weak_running.clone();
639            move |pane, dragged_item, _window, cx| {
640                if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
641                    let is_current_pane = tab.pane == cx.entity();
642                    let Some(can_drag_away) = weak_running
643                        .read_with(cx, |running_state, _| {
644                            let current_panes = running_state.panes.panes();
645                            !current_panes.contains(&&tab.pane)
646                                || current_panes.len() > 1
647                                || (!is_current_pane || pane.items_len() > 1)
648                        })
649                        .ok()
650                    else {
651                        return false;
652                    };
653                    if can_drag_away {
654                        let item = if is_current_pane {
655                            pane.item_for_index(tab.ix)
656                        } else {
657                            tab.pane.read(cx).item_for_index(tab.ix)
658                        };
659                        if let Some(item) = item {
660                            return item.downcast::<SubView>().is_some();
661                        }
662                    }
663                }
664                false
665            }
666        })));
667        pane.set_can_toggle_zoom(false, cx);
668        pane.display_nav_history_buttons(None);
669        pane.set_should_display_tab_bar(|_, _| true);
670        pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));
671        pane.set_render_tab_bar(cx, {
672            move |pane, window, cx| render_debugger_tab_bar(pane, &focus_handle, window, cx)
673        });
674        pane
675    })
676}
677
678pub struct DebugTerminal {
679    pub terminal: Option<Entity<TerminalView>>,
680    focus_handle: FocusHandle,
681    _subscriptions: [Subscription; 1],
682}
683
684impl DebugTerminal {
685    fn empty(window: &mut Window, cx: &mut Context<Self>) -> Self {
686        let focus_handle = cx.focus_handle();
687        let focus_subscription = cx.on_focus(&focus_handle, window, |this, window, cx| {
688            if let Some(terminal) = this.terminal.as_ref() {
689                terminal.focus_handle(cx).focus(window, cx);
690            }
691        });
692
693        Self {
694            terminal: None,
695            focus_handle,
696            _subscriptions: [focus_subscription],
697        }
698    }
699}
700
701impl gpui::Render for DebugTerminal {
702    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
703        div()
704            .track_focus(&self.focus_handle)
705            .size_full()
706            .bg(cx.theme().colors().editor_background)
707            .children(self.terminal.clone())
708    }
709}
710impl Focusable for DebugTerminal {
711    fn focus_handle(&self, _cx: &App) -> FocusHandle {
712        self.focus_handle.clone()
713    }
714}
715
716impl RunningState {
717    // todo(debugger) move this to util and make it so you pass a closure to it that converts a string
718    pub(crate) fn substitute_variables_in_config(
719        config: &mut serde_json::Value,
720        context: &TaskContext,
721    ) {
722        match config {
723            serde_json::Value::Object(obj) => {
724                obj.values_mut()
725                    .for_each(|value| Self::substitute_variables_in_config(value, context));
726            }
727            serde_json::Value::Array(array) => {
728                array
729                    .iter_mut()
730                    .for_each(|value| Self::substitute_variables_in_config(value, context));
731            }
732            serde_json::Value::String(s) => {
733                // Some built-in zed tasks wrap their arguments in quotes as they might contain spaces.
734                if s.starts_with("\"$ZED_") && s.ends_with('"') {
735                    *s = s[1..s.len() - 1].to_string();
736                }
737                if let Some(substituted) = substitute_variables_in_str(s, context) {
738                    *s = substituted;
739                }
740            }
741            _ => {}
742        }
743    }
744
745    pub(crate) fn contains_substring(config: &serde_json::Value, substring: &str) -> bool {
746        match config {
747            serde_json::Value::Object(obj) => obj
748                .values()
749                .any(|value| Self::contains_substring(value, substring)),
750            serde_json::Value::Array(array) => array
751                .iter()
752                .any(|value| Self::contains_substring(value, substring)),
753            serde_json::Value::String(s) => s.contains(substring),
754            _ => false,
755        }
756    }
757
758    pub(crate) fn substitute_process_id_in_config(config: &mut serde_json::Value, process_id: i32) {
759        match config {
760            serde_json::Value::Object(obj) => {
761                obj.values_mut().for_each(|value| {
762                    Self::substitute_process_id_in_config(value, process_id);
763                });
764            }
765            serde_json::Value::Array(array) => {
766                array.iter_mut().for_each(|value| {
767                    Self::substitute_process_id_in_config(value, process_id);
768                });
769            }
770            serde_json::Value::String(s) => {
771                if s.contains(PROCESS_ID_PLACEHOLDER.as_str()) {
772                    *s = s.replace(PROCESS_ID_PLACEHOLDER.as_str(), &process_id.to_string());
773                }
774            }
775            _ => {}
776        }
777    }
778
779    pub(crate) fn relativize_paths(
780        key: Option<&str>,
781        config: &mut serde_json::Value,
782        context: &TaskContext,
783    ) {
784        match config {
785            serde_json::Value::Object(obj) => {
786                obj.iter_mut()
787                    .for_each(|(key, value)| Self::relativize_paths(Some(key), value, context));
788            }
789            serde_json::Value::Array(array) => {
790                array
791                    .iter_mut()
792                    .for_each(|value| Self::relativize_paths(None, value, context));
793            }
794            serde_json::Value::String(s) if key == Some("program") || key == Some("cwd") => {
795                // Some built-in zed tasks wrap their arguments in quotes as they might contain spaces.
796                if s.starts_with("\"$ZED_") && s.ends_with('"') {
797                    *s = s[1..s.len() - 1].to_string();
798                }
799                resolve_path(s);
800
801                if let Some(substituted) = substitute_variables_in_str(s, context) {
802                    *s = substituted;
803                }
804            }
805            _ => {}
806        }
807    }
808
809    pub(crate) fn new(
810        session: Entity<Session>,
811        project: Entity<Project>,
812        workspace: WeakEntity<Workspace>,
813        parent_terminal: Option<Entity<DebugTerminal>>,
814        serialized_pane_layout: Option<SerializedLayout>,
815        dock_axis: Axis,
816        window: &mut Window,
817        cx: &mut Context<Self>,
818    ) -> Self {
819        let focus_handle = cx.focus_handle();
820        let session_id = session.read(cx).session_id();
821        let weak_project = project.downgrade();
822        let weak_state = cx.weak_entity();
823        let stack_frame_list = cx.new(|cx| {
824            StackFrameList::new(
825                workspace.clone(),
826                session.clone(),
827                weak_state.clone(),
828                window,
829                cx,
830            )
831        });
832
833        let debug_terminal =
834            parent_terminal.unwrap_or_else(|| cx.new(|cx| DebugTerminal::empty(window, cx)));
835        let memory_view = cx.new(|cx| {
836            MemoryView::new(
837                session.clone(),
838                workspace.clone(),
839                stack_frame_list.downgrade(),
840                window,
841                cx,
842            )
843        });
844        let variable_list = cx.new(|cx| {
845            VariableList::new(
846                session.clone(),
847                stack_frame_list.clone(),
848                memory_view.clone(),
849                weak_state.clone(),
850                window,
851                cx,
852            )
853        });
854
855        let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
856
857        let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
858
859        let console = cx.new(|cx| {
860            Console::new(
861                session.clone(),
862                stack_frame_list.clone(),
863                variable_list.clone(),
864                window,
865                cx,
866            )
867        });
868
869        let breakpoint_list = BreakpointList::new(
870            Some(session.clone()),
871            workspace.clone(),
872            &project,
873            window,
874            cx,
875        );
876
877        let _subscriptions = vec![
878            cx.on_app_quit(move |this, cx| {
879                let shutdown = this
880                    .session
881                    .update(cx, |session, cx| session.on_app_quit(cx));
882                let terminal = this.debug_terminal.clone();
883                async move {
884                    shutdown.await;
885                    drop(terminal)
886                }
887            }),
888            cx.observe(&module_list, |_, _, cx| cx.notify()),
889            cx.subscribe_in(&session, window, |this, _, event, window, cx| {
890                match event {
891                    SessionEvent::Stopped(thread_id) => {
892                        let panel = this
893                            .workspace
894                            .update(cx, |workspace, cx| {
895                                workspace.open_panel::<crate::DebugPanel>(window, cx);
896                                workspace.panel::<crate::DebugPanel>(cx)
897                            })
898                            .log_err()
899                            .flatten();
900
901                        if let Some(thread_id) = thread_id {
902                            this.select_thread(*thread_id, window, cx);
903                        }
904                        if let Some(panel) = panel {
905                            let id = this.session_id;
906                            window.defer(cx, move |window, cx| {
907                                panel.update(cx, |this, cx| {
908                                    this.activate_session_by_id(id, window, cx);
909                                })
910                            })
911                        }
912                    }
913                    SessionEvent::Threads => {
914                        let threads = this.session.update(cx, |this, cx| this.threads(cx));
915                        this.select_current_thread(&threads, window, cx);
916                    }
917                    SessionEvent::CapabilitiesLoaded => {
918                        let capabilities = this.capabilities(cx);
919                        if !capabilities.supports_modules_request.unwrap_or(false) {
920                            this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
921                        }
922                        if !capabilities
923                            .supports_loaded_sources_request
924                            .unwrap_or(false)
925                        {
926                            this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
927                        }
928                    }
929                    SessionEvent::RunInTerminal { request, sender } => this
930                        .handle_run_in_terminal(request, sender.clone(), window, cx)
931                        .detach_and_log_err(cx),
932
933                    _ => {}
934                }
935                cx.notify()
936            }),
937            cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
938                this.serialize_layout(window, cx);
939            }),
940            cx.subscribe(
941                &session,
942                |this, session, event: &SessionStateEvent, cx| match event {
943                    SessionStateEvent::Shutdown if session.read(cx).is_building() => {
944                        this.shutdown(cx);
945                    }
946                    _ => {}
947                },
948            ),
949        ];
950
951        let mut pane_close_subscriptions = HashMap::default();
952        let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
953            persistence::deserialize_pane_layout(
954                serialized_layout.panes,
955                dock_axis != serialized_layout.dock_axis,
956                &workspace,
957                &project,
958                &stack_frame_list,
959                &variable_list,
960                &module_list,
961                &console,
962                &breakpoint_list,
963                &loaded_source_list,
964                &debug_terminal,
965                &memory_view,
966                &mut pane_close_subscriptions,
967                window,
968                cx,
969            )
970        }) {
971            workspace::PaneGroup::with_root(root)
972        } else {
973            pane_close_subscriptions.clear();
974
975            let root = Self::default_pane_layout(
976                project,
977                &workspace,
978                &stack_frame_list,
979                &variable_list,
980                &console,
981                &breakpoint_list,
982                &debug_terminal,
983                dock_axis,
984                &mut pane_close_subscriptions,
985                window,
986                cx,
987            );
988
989            workspace::PaneGroup::with_root(root)
990        };
991        let active_pane = panes.first_pane();
992
993        Self {
994            memory_view,
995            session,
996            workspace,
997            project: weak_project,
998            focus_handle,
999            variable_list,
1000            _subscriptions,
1001            thread_id: None,
1002            _remote_id: None,
1003            stack_frame_list,
1004            session_id,
1005            panes,
1006            active_pane,
1007            module_list,
1008            console,
1009            breakpoint_list,
1010            loaded_sources_list: loaded_source_list,
1011            pane_close_subscriptions,
1012            debug_terminal,
1013            dock_axis,
1014            _schedule_serialize: None,
1015            scenario: None,
1016            scenario_context: None,
1017        }
1018    }
1019
1020    pub(crate) fn remove_pane_item(
1021        &mut self,
1022        item_kind: DebuggerPaneItem,
1023        window: &mut Window,
1024        cx: &mut Context<Self>,
1025    ) {
1026        if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
1027            Some(pane).zip(
1028                pane.read(cx)
1029                    .items()
1030                    .find(|item| {
1031                        item.act_as::<SubView>(cx)
1032                            .is_some_and(|view| view.read(cx).kind == item_kind)
1033                    })
1034                    .map(|item| item.item_id()),
1035            )
1036        }) {
1037            pane.update(cx, |pane, cx| {
1038                pane.remove_item(item_id, false, true, window, cx)
1039            })
1040        }
1041    }
1042
1043    pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
1044        self.panes.pane_at_pixel_position(position).is_some()
1045    }
1046
1047    pub(crate) fn resolve_scenario(
1048        &self,
1049        scenario: DebugScenario,
1050        task_context: SharedTaskContext,
1051        buffer: Option<Entity<Buffer>>,
1052        worktree_id: Option<WorktreeId>,
1053        window: &Window,
1054        cx: &mut Context<Self>,
1055    ) -> Task<Result<DebugTaskDefinition>> {
1056        let Some(workspace) = self.workspace.upgrade() else {
1057            return Task::ready(Err(anyhow!("no workspace")));
1058        };
1059        let project = workspace.read(cx).project().clone();
1060        let dap_store = project.read(cx).dap_store().downgrade();
1061        let dap_registry = cx.global::<DapRegistry>().clone();
1062        let task_store = project.read(cx).task_store().downgrade();
1063        let weak_project = project.downgrade();
1064        let weak_workspace = workspace.downgrade();
1065        let is_windows = project.read(cx).path_style(cx).is_windows();
1066        let remote_shell = project
1067            .read(cx)
1068            .remote_client()
1069            .as_ref()
1070            .and_then(|remote| remote.read(cx).shell());
1071
1072        cx.spawn_in(window, async move |this, cx| {
1073            let DebugScenario {
1074                adapter,
1075                label,
1076                build,
1077                mut config,
1078                tcp_connection,
1079            } = scenario;
1080            Self::relativize_paths(None, &mut config, &task_context);
1081            Self::substitute_variables_in_config(&mut config, &task_context);
1082
1083            if Self::contains_substring(&config, PROCESS_ID_PLACEHOLDER.as_str()) || label.as_ref().contains(PROCESS_ID_PLACEHOLDER.as_str()) {
1084                let (tx, rx) = futures::channel::oneshot::channel::<Option<i32>>();
1085
1086                let weak_workspace_clone = weak_workspace.clone();
1087                weak_workspace.update_in(cx, |workspace, window, cx| {
1088                    let project = workspace.project().clone();
1089                    workspace.toggle_modal(window, cx, |window, cx| {
1090                        AttachModal::new(
1091                            ModalIntent::ResolveProcessId(Some(tx)),
1092                            weak_workspace_clone,
1093                            project,
1094                            true,
1095                            window,
1096                            cx,
1097                        )
1098                    });
1099                }).ok();
1100
1101                let Some(process_id) = rx.await.ok().flatten() else {
1102                    bail!("No process selected with config that contains {}", PROCESS_ID_PLACEHOLDER.as_str())
1103                };
1104
1105                Self::substitute_process_id_in_config(&mut config, process_id);
1106            }
1107
1108            let request_type = match dap_registry
1109                .adapter(&adapter)
1110                .with_context(|| format!("{}: is not a valid adapter name", &adapter)) {
1111                    Ok(adapter) => adapter.request_kind(&config).await,
1112                    Err(e) => Err(e)
1113                };
1114
1115
1116            let config_is_valid = request_type.is_ok();
1117            let mut extra_config = Value::Null;
1118            let build_output = if let Some(build) = build {
1119                let (task_template, locator_name) = match build {
1120                    BuildTaskDefinition::Template {
1121                        task_template,
1122                        locator_name,
1123                    } => (task_template, locator_name),
1124                    BuildTaskDefinition::ByName(ref label) => {
1125                        let task = task_store.update(cx, |this, cx| {
1126                            this.task_inventory().map(|inventory| {
1127                                inventory.read(cx).task_template_by_label(
1128                                    buffer,
1129                                    worktree_id,
1130                                    label,
1131                                    cx,
1132                                )
1133                            })
1134                        })?;
1135                        let task = match task {
1136                            Some(task) => task.await,
1137                            None => None,
1138                        }.with_context(|| format!("Couldn't find task template for {build:?}"))?;
1139                        (task, None)
1140                    }
1141                };
1142                let Some(mut task) = task_template.resolve_task("debug-build-task", &task_context) else {
1143                    anyhow::bail!("Could not resolve task variables within a debug scenario");
1144                };
1145
1146                let locator_name = if let Some(locator_name) = locator_name {
1147                    extra_config = config.clone();
1148                    debug_assert!(!config_is_valid);
1149                    Some(locator_name)
1150                } else if !config_is_valid {
1151                    let task = dap_store
1152                        .update(cx, |this, cx| {
1153                            this.debug_scenario_for_build_task(
1154                                task.original_task().clone(),
1155                                adapter.clone().into(),
1156                                task.display_label().to_owned().into(),
1157                                cx,
1158                            )
1159
1160                        });
1161                    if let Ok(t) = task {
1162                        t.await.and_then(|scenario| {
1163                            extra_config = scenario.config;
1164                            match scenario.build {
1165                                Some(BuildTaskDefinition::Template {
1166                                    locator_name, ..
1167                                }) => locator_name,
1168                                _ => None,
1169                            }
1170                        })
1171                    } else {
1172                        None
1173                    }
1174
1175                } else {
1176                    None
1177                };
1178
1179                if let Some(remote_shell) = remote_shell && task.resolved.shell == Shell::System {
1180                    task.resolved.shell = Shell::Program(remote_shell);
1181                }
1182
1183                let builder = ShellBuilder::new(&task.resolved.shell, is_windows);
1184                let command_label = builder.command_label(task.resolved.command.as_deref().unwrap_or(""));
1185                let (command, args) =
1186                    builder.build(task.resolved.command.clone(), &task.resolved.args);
1187
1188                let task_with_shell = SpawnInTerminal {
1189                    command_label,
1190                    command: Some(command),
1191                    args,
1192                    ..task.resolved.clone()
1193                };
1194
1195                Workspace::save_for_task(&weak_workspace, task_with_shell.save, cx).await;
1196
1197                let terminal = project
1198                    .update(cx, |project, cx| {
1199                        project.create_terminal_task(
1200                            task_with_shell.clone(),
1201                            cx,
1202                        )
1203                    }).await?;
1204
1205                let terminal_view = cx.new_window_entity(|window, cx| {
1206                    TerminalView::new(
1207                        terminal.clone(),
1208                        weak_workspace,
1209                        None,
1210                        weak_project,
1211                        window,
1212                        cx,
1213                    )
1214                })?;
1215
1216                this.update_in(cx, |this, window, cx| {
1217                    this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
1218                    this.debug_terminal.update(cx, |debug_terminal, cx| {
1219                        debug_terminal.terminal = Some(terminal_view);
1220                        cx.notify();
1221                    });
1222                })?;
1223
1224                let exit_status = terminal
1225                    .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))
1226                    .await
1227                    .context("Failed to wait for completed task")?;
1228
1229                if !exit_status.success() {
1230                    anyhow::bail!("Build failed");
1231                }
1232                Some((task.resolved.clone(), locator_name, extra_config))
1233            } else {
1234                None
1235            };
1236
1237            if config_is_valid {
1238            } else if let Some((task, locator_name, extra_config)) = build_output {
1239                let locator_name =
1240                    locator_name.with_context(|| {
1241                        format!("Could not find a valid locator for a build task and configure is invalid with error: {}", request_type.err()
1242                            .map(|err| err.to_string())
1243                            .unwrap_or_default())
1244                    })?;
1245                let request = dap_store
1246                    .update(cx, |this, cx| {
1247                        this.run_debug_locator(&locator_name, task, cx)
1248                    })?
1249                    .await?;
1250
1251                let zed_config = ZedDebugConfig {
1252                    label: label.clone(),
1253                    adapter: adapter.clone(),
1254                    request,
1255                    stop_on_entry: None,
1256                };
1257
1258                let scenario = dap_registry
1259                    .adapter(&adapter)
1260                    .with_context(|| anyhow!("{}: is not a valid adapter name", &adapter))?.config_from_zed_format(zed_config)
1261                    .await?;
1262                config = scenario.config;
1263                util::merge_non_null_json_value_into(extra_config, &mut config);
1264
1265                Self::substitute_variables_in_config(&mut config, &task_context);
1266            } else {
1267                let Err(e) = request_type else {
1268                    unreachable!();
1269                };
1270                anyhow::bail!("Omega cannot determine how to run this debug scenario. `build` field was not provided and Debug Adapter won't accept provided configuration because: {e}");
1271            };
1272
1273            Ok(DebugTaskDefinition {
1274                label,
1275                adapter: DebugAdapterName(adapter),
1276                config,
1277                tcp_connection,
1278            })
1279        })
1280    }
1281
1282    fn handle_run_in_terminal(
1283        &self,
1284        request: &RunInTerminalRequestArguments,
1285        mut sender: mpsc::Sender<Result<u32>>,
1286        window: &mut Window,
1287        cx: &mut Context<Self>,
1288    ) -> Task<Result<()>> {
1289        let running = cx.entity();
1290        let Ok(project) = self
1291            .workspace
1292            .read_with(cx, |workspace, _| workspace.project().clone())
1293        else {
1294            return Task::ready(Err(anyhow!("no workspace")));
1295        };
1296        let session = self.session.read(cx);
1297
1298        let cwd = (!request.cwd.is_empty())
1299            .then(|| PathBuf::from(&request.cwd))
1300            .or_else(|| session.binary().unwrap().cwd.clone());
1301
1302        let mut envs: HashMap<String, String> =
1303            self.session.read(cx).task_context().project_env.clone();
1304        if let Some(Value::Object(env)) = &request.env {
1305            for (key, value) in env {
1306                let value_str = match (key.as_str(), value) {
1307                    (_, Value::String(value)) => value,
1308                    _ => continue,
1309                };
1310
1311                envs.insert(key.clone(), value_str.clone());
1312            }
1313        }
1314
1315        let mut args = request.args.clone();
1316        let command = if envs.contains_key("VSCODE_INSPECTOR_OPTIONS") {
1317            // Handle special case for NodeJS debug adapter
1318            // If the Node binary path is provided (possibly with arguments like --experimental-network-inspection),
1319            // we set the command to None
1320            // This prevents the NodeJS REPL from appearing, which is not the desired behavior
1321            // The expected usage is for users to provide their own Node command, e.g., `node test.js`
1322            // This allows the NodeJS debug client to attach correctly
1323            if args
1324                .iter()
1325                .filter(|arg| !arg.starts_with("--"))
1326                .collect::<Vec<_>>()
1327                .len()
1328                > 1
1329            {
1330                Some(args.remove(0))
1331            } else {
1332                None
1333            }
1334        } else if !args.is_empty() {
1335            Some(args.remove(0))
1336        } else {
1337            None
1338        };
1339
1340        let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
1341        let title = request
1342            .title
1343            .clone()
1344            .filter(|title| !title.is_empty())
1345            .or_else(|| command.clone())
1346            .unwrap_or_else(|| "Debug terminal".to_string());
1347        let kind = task::SpawnInTerminal {
1348            id: task::TaskId("debug".to_string()),
1349            full_label: title.clone(),
1350            label: title.clone(),
1351            command,
1352            args,
1353            command_label: title,
1354            cwd,
1355            env: envs,
1356            use_new_terminal: true,
1357            allow_concurrent_runs: true,
1358            reveal: task::RevealStrategy::NoFocus,
1359            reveal_target: task::RevealTarget::Dock,
1360            hide: task::HideStrategy::Never,
1361            shell,
1362            show_summary: false,
1363            show_command: false,
1364            show_rerun: false,
1365            save: task::SaveStrategy::default(),
1366        };
1367
1368        let workspace = self.workspace.clone();
1369        let weak_project = project.downgrade();
1370
1371        let terminal_task =
1372            project.update(cx, |project, cx| project.create_terminal_task(kind, cx));
1373        let terminal_task = cx.spawn_in(window, async move |_, cx| {
1374            let terminal = terminal_task.await?;
1375
1376            let terminal_view = cx.new_window_entity(|window, cx| {
1377                TerminalView::new(terminal.clone(), workspace, None, weak_project, window, cx)
1378            })?;
1379
1380            running.update_in(cx, |running, window, cx| {
1381                running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
1382                running.debug_terminal.update(cx, |debug_terminal, cx| {
1383                    debug_terminal.terminal = Some(terminal_view);
1384                    cx.notify();
1385                });
1386            })?;
1387
1388            terminal.read_with(cx, |terminal, _| {
1389                terminal
1390                    .pid()
1391                    .map(|pid| pid.as_u32())
1392                    .context("Terminal was spawned but PID was not available")
1393            })
1394        });
1395
1396        cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
1397    }
1398
1399    fn create_sub_view(
1400        &self,
1401        item_kind: DebuggerPaneItem,
1402        pane: &Entity<Pane>,
1403        cx: &mut Context<Self>,
1404    ) -> Box<dyn ItemHandle> {
1405        let running_state = cx.weak_entity();
1406        let host_pane = pane.downgrade();
1407
1408        match item_kind {
1409            DebuggerPaneItem::Console => Box::new(SubView::console(
1410                self.console.clone(),
1411                running_state,
1412                host_pane,
1413                cx,
1414            )),
1415            DebuggerPaneItem::Variables => Box::new(SubView::new(
1416                self.variable_list.focus_handle(cx),
1417                self.variable_list.clone().into(),
1418                item_kind,
1419                running_state,
1420                host_pane,
1421                cx,
1422            )),
1423            DebuggerPaneItem::BreakpointList => Box::new(SubView::breakpoint_list(
1424                self.breakpoint_list.clone(),
1425                running_state,
1426                host_pane,
1427                cx,
1428            )),
1429            DebuggerPaneItem::Frames => Box::new(SubView::new(
1430                self.stack_frame_list.focus_handle(cx),
1431                self.stack_frame_list.clone().into(),
1432                item_kind,
1433                running_state,
1434                host_pane,
1435                cx,
1436            )),
1437            DebuggerPaneItem::Modules => Box::new(SubView::new(
1438                self.module_list.focus_handle(cx),
1439                self.module_list.clone().into(),
1440                item_kind,
1441                running_state,
1442                host_pane,
1443                cx,
1444            )),
1445            DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
1446                self.loaded_sources_list.focus_handle(cx),
1447                self.loaded_sources_list.clone().into(),
1448                item_kind,
1449                running_state,
1450                host_pane,
1451                cx,
1452            )),
1453            DebuggerPaneItem::Terminal => Box::new(SubView::new(
1454                self.debug_terminal.focus_handle(cx),
1455                self.debug_terminal.clone().into(),
1456                item_kind,
1457                running_state,
1458                host_pane,
1459                cx,
1460            )),
1461            DebuggerPaneItem::MemoryView => Box::new(SubView::new(
1462                self.memory_view.focus_handle(cx),
1463                self.memory_view.clone().into(),
1464                item_kind,
1465                running_state,
1466                host_pane,
1467                cx,
1468            )),
1469        }
1470    }
1471
1472    pub(crate) fn ensure_pane_item(
1473        &mut self,
1474        item_kind: DebuggerPaneItem,
1475        window: &mut Window,
1476        cx: &mut Context<Self>,
1477    ) {
1478        if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
1479            return;
1480        };
1481        let pane = self.panes.last_pane();
1482        let sub_view = self.create_sub_view(item_kind, &pane, cx);
1483
1484        pane.update(cx, |pane, cx| {
1485            pane.add_item_inner(sub_view, false, false, false, None, window, cx);
1486        })
1487    }
1488
1489    pub(crate) fn add_pane_item(
1490        &mut self,
1491        item_kind: DebuggerPaneItem,
1492        position: Point<Pixels>,
1493        window: &mut Window,
1494        cx: &mut Context<Self>,
1495    ) {
1496        debug_assert!(
1497            item_kind.is_supported(self.session.read(cx).capabilities()),
1498            "We should only allow adding supported item kinds"
1499        );
1500
1501        if let Some(pane) = self.panes.pane_at_pixel_position(position) {
1502            let sub_view = self.create_sub_view(item_kind, pane, cx);
1503
1504            pane.update(cx, |pane, cx| {
1505                pane.add_item(sub_view, false, false, None, window, cx);
1506            })
1507        }
1508    }
1509
1510    pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
1511        let caps = self.session.read(cx).capabilities();
1512        let mut pane_item_status = IndexMap::from_iter(
1513            DebuggerPaneItem::all()
1514                .iter()
1515                .filter(|kind| kind.is_supported(caps))
1516                .map(|kind| (*kind, false)),
1517        );
1518        self.panes.panes().iter().for_each(|pane| {
1519            pane.read(cx)
1520                .items()
1521                .filter_map(|item| item.act_as::<SubView>(cx))
1522                .for_each(|view| {
1523                    pane_item_status.insert(view.read(cx).kind, true);
1524                });
1525        });
1526
1527        pane_item_status
1528    }
1529
1530    pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1531        if self._schedule_serialize.is_none() {
1532            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
1533                cx.background_executor()
1534                    .timer(Duration::from_millis(100))
1535                    .await;
1536
1537                let Some((adapter_name, pane_layout)) = this
1538                    .read_with(cx, |this, cx| {
1539                        let adapter_name = this.session.read(cx).adapter();
1540                        (
1541                            adapter_name,
1542                            persistence::build_serialized_layout(
1543                                &this.panes.root,
1544                                this.dock_axis,
1545                                cx,
1546                            ),
1547                        )
1548                    })
1549                    .ok()
1550                else {
1551                    return;
1552                };
1553
1554                let kvp = this
1555                    .read_with(cx, |_, cx| db::kvp::KeyValueStore::global(cx))
1556                    .ok();
1557                if let Some(kvp) = kvp {
1558                    persistence::serialize_pane_layout(adapter_name, pane_layout, kvp)
1559                        .await
1560                        .log_err();
1561                }
1562
1563                this.update(cx, |this, _| {
1564                    this._schedule_serialize.take();
1565                })
1566                .ok();
1567            }));
1568        }
1569    }
1570
1571    pub(crate) fn handle_pane_event(
1572        this: &mut RunningState,
1573        source_pane: &Entity<Pane>,
1574        event: &Event,
1575        window: &mut Window,
1576        cx: &mut Context<RunningState>,
1577    ) {
1578        this.serialize_layout(window, cx);
1579        match event {
1580            Event::AddItem { item } => {
1581                if let Some(sub_view) = item.downcast::<SubView>() {
1582                    sub_view.update(cx, |sub_view, _| {
1583                        sub_view.set_host_pane(source_pane.downgrade());
1584                    });
1585                }
1586            }
1587            Event::Remove { .. } => {
1588                let _did_find_pane = this.panes.remove(source_pane, cx).is_ok();
1589                debug_assert!(_did_find_pane);
1590                cx.notify();
1591            }
1592            Event::Focus => {
1593                this.active_pane = source_pane.clone();
1594            }
1595            _ => {}
1596        }
1597    }
1598
1599    pub(crate) fn activate_pane_in_direction(
1600        &mut self,
1601        direction: SplitDirection,
1602        window: &mut Window,
1603        cx: &mut Context<Self>,
1604    ) {
1605        let active_pane = self.active_pane.clone();
1606        if let Some(pane) = self
1607            .panes
1608            .find_pane_in_direction(&active_pane, direction, cx)
1609        {
1610            pane.update(cx, |pane, cx| {
1611                pane.focus_active_item(window, cx);
1612            })
1613        } else {
1614            self.workspace
1615                .update(cx, |workspace, cx| {
1616                    workspace.activate_pane_in_direction(direction, window, cx)
1617                })
1618                .ok();
1619        }
1620    }
1621
1622    pub(crate) fn go_to_selected_stack_frame(&self, window: &mut Window, cx: &mut Context<Self>) {
1623        if self.thread_id.is_some() {
1624            self.stack_frame_list
1625                .update(cx, |list, cx| {
1626                    let Some(stack_frame_id) = list.opened_stack_frame_id() else {
1627                        return Task::ready(Ok(()));
1628                    };
1629                    list.go_to_stack_frame(stack_frame_id, window, cx)
1630                })
1631                .detach();
1632        }
1633    }
1634
1635    pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
1636        self.variable_list.read(cx).has_open_context_menu()
1637    }
1638
1639    pub fn session(&self) -> &Entity<Session> {
1640        &self.session
1641    }
1642
1643    pub fn session_id(&self) -> SessionId {
1644        self.session_id
1645    }
1646
1647    pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
1648        self.stack_frame_list.read(cx).opened_stack_frame_id()
1649    }
1650
1651    pub(crate) fn stack_frame_list(&self) -> &Entity<StackFrameList> {
1652        &self.stack_frame_list
1653    }
1654
1655    #[cfg(test)]
1656    pub fn console(&self) -> &Entity<Console> {
1657        &self.console
1658    }
1659
1660    #[cfg(test)]
1661    pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
1662        &self.module_list
1663    }
1664
1665    pub(crate) fn activate_item(
1666        &mut self,
1667        item: DebuggerPaneItem,
1668        window: &mut Window,
1669        cx: &mut Context<Self>,
1670    ) {
1671        self.ensure_pane_item(item, window, cx);
1672
1673        let (variable_list_position, pane) = self
1674            .panes
1675            .panes()
1676            .into_iter()
1677            .find_map(|pane| {
1678                pane.read(cx)
1679                    .items_of_type::<SubView>()
1680                    .position(|view| view.read(cx).view_kind() == item)
1681                    .map(|view| (view, pane))
1682            })
1683            .unwrap();
1684
1685        pane.update(cx, |this, cx| {
1686            this.activate_item(variable_list_position, true, true, window, cx);
1687        });
1688    }
1689
1690    #[cfg(test)]
1691    pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
1692        &self.variable_list
1693    }
1694
1695    #[cfg(test)]
1696    pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
1697        persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
1698    }
1699
1700    pub fn capabilities(&self, cx: &App) -> Capabilities {
1701        self.session().read(cx).capabilities().clone()
1702    }
1703
1704    pub fn select_current_thread(
1705        &mut self,
1706        threads: &Vec<(Thread, ThreadStatus)>,
1707        window: &mut Window,
1708        cx: &mut Context<Self>,
1709    ) {
1710        let selected_thread = self
1711            .thread_id
1712            .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
1713            .or_else(|| threads.first());
1714
1715        let Some((selected_thread, _)) = selected_thread else {
1716            return;
1717        };
1718
1719        if Some(ThreadId(selected_thread.id)) != self.thread_id {
1720            self.select_thread(ThreadId(selected_thread.id), window, cx);
1721        }
1722    }
1723
1724    pub fn selected_thread_id(&self) -> Option<ThreadId> {
1725        self.thread_id
1726    }
1727
1728    pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
1729        self.thread_id
1730            .map(|id| self.session().read(cx).thread_status(id))
1731    }
1732
1733    pub(crate) fn select_thread(
1734        &mut self,
1735        thread_id: ThreadId,
1736        window: &mut Window,
1737        cx: &mut Context<Self>,
1738    ) {
1739        if self.thread_id.is_some_and(|id| id == thread_id) {
1740            return;
1741        }
1742
1743        self.thread_id = Some(thread_id);
1744
1745        self.stack_frame_list
1746            .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
1747    }
1748
1749    pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
1750        let Some(thread_id) = self.thread_id else {
1751            return;
1752        };
1753
1754        self.session().update(cx, |state, cx| {
1755            state.continue_thread(thread_id, cx);
1756        });
1757    }
1758
1759    pub fn step_over(&mut self, cx: &mut Context<Self>) {
1760        let Some(thread_id) = self.thread_id else {
1761            return;
1762        };
1763
1764        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1765
1766        self.session().update(cx, |state, cx| {
1767            state.step_over(thread_id, granularity, cx);
1768        });
1769    }
1770
1771    pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
1772        let Some(thread_id) = self.thread_id else {
1773            return;
1774        };
1775
1776        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1777
1778        self.session().update(cx, |state, cx| {
1779            state.step_in(thread_id, granularity, cx);
1780        });
1781    }
1782
1783    pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
1784        let Some(thread_id) = self.thread_id else {
1785            return;
1786        };
1787
1788        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1789
1790        self.session().update(cx, |state, cx| {
1791            state.step_out(thread_id, granularity, cx);
1792        });
1793    }
1794
1795    pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
1796        let Some(thread_id) = self.thread_id else {
1797            return;
1798        };
1799
1800        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1801
1802        self.session().update(cx, |state, cx| {
1803            state.step_back(thread_id, granularity, cx);
1804        });
1805    }
1806
1807    pub fn rerun_session(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1808        if let Some((scenario, context)) = self.scenario.take().zip(self.scenario_context.take())
1809            && scenario.build.is_some()
1810        {
1811            let DebugScenarioContext {
1812                task_context,
1813                active_buffer,
1814                worktree_id,
1815            } = context;
1816            let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());
1817
1818            self.workspace
1819                .update(cx, |workspace, cx| {
1820                    workspace.start_debug_session(
1821                        scenario,
1822                        task_context,
1823                        active_buffer,
1824                        worktree_id,
1825                        window,
1826                        cx,
1827                    )
1828                })
1829                .ok();
1830        } else {
1831            self.restart_session(cx);
1832        }
1833    }
1834
1835    pub fn restart_session(&self, cx: &mut Context<Self>) {
1836        self.session().update(cx, |state, cx| {
1837            state.restart(None, cx);
1838        });
1839    }
1840
1841    pub fn pause_thread(&self, cx: &mut Context<Self>) {
1842        let Some(thread_id) = self.thread_id else {
1843            return;
1844        };
1845
1846        self.session().update(cx, |state, cx| {
1847            state.pause_thread(thread_id, cx);
1848        });
1849    }
1850
1851    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
1852        self.workspace
1853            .update(cx, |workspace, cx| {
1854                workspace
1855                    .project()
1856                    .read(cx)
1857                    .breakpoint_store()
1858                    .update(cx, |store, cx| {
1859                        store.remove_active_position(Some(self.session_id), cx)
1860                    })
1861            })
1862            .log_err();
1863
1864        let is_building = self.session.update(cx, |session, cx| {
1865            session.shutdown(cx).detach();
1866            matches!(session.state, session::SessionState::Booting(_))
1867        });
1868
1869        if is_building {
1870            self.debug_terminal.update(cx, |terminal, cx| {
1871                if let Some(view) = terminal.terminal.as_ref() {
1872                    view.update(cx, |view, cx| {
1873                        view.terminal()
1874                            .update(cx, |terminal, _| terminal.kill_active_task())
1875                    })
1876                }
1877            })
1878        }
1879    }
1880
1881    pub fn stop_thread(&self, cx: &mut Context<Self>) {
1882        let Some(thread_id) = self.thread_id else {
1883            return;
1884        };
1885
1886        self.workspace
1887            .update(cx, |workspace, cx| {
1888                workspace
1889                    .project()
1890                    .read(cx)
1891                    .breakpoint_store()
1892                    .update(cx, |store, cx| {
1893                        store.remove_active_position(Some(self.session_id), cx)
1894                    })
1895            })
1896            .log_err();
1897
1898        self.session().update(cx, |state, cx| {
1899            state.terminate_threads(Some(vec![thread_id; 1]), cx);
1900        });
1901    }
1902
1903    pub fn detach_client(&self, cx: &mut Context<Self>) {
1904        self.session().update(cx, |state, cx| {
1905            state.disconnect_client(cx);
1906        });
1907    }
1908
1909    pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1910        self.session.update(cx, |session, cx| {
1911            session.toggle_ignore_breakpoints(cx).detach();
1912        });
1913    }
1914
1915    fn default_pane_layout(
1916        project: Entity<Project>,
1917        workspace: &WeakEntity<Workspace>,
1918        stack_frame_list: &Entity<StackFrameList>,
1919        variable_list: &Entity<VariableList>,
1920        console: &Entity<Console>,
1921        breakpoints: &Entity<BreakpointList>,
1922        debug_terminal: &Entity<DebugTerminal>,
1923        dock_axis: Axis,
1924        subscriptions: &mut HashMap<EntityId, Subscription>,
1925        window: &mut Window,
1926        cx: &mut Context<'_, RunningState>,
1927    ) -> Member {
1928        let running_state = cx.weak_entity();
1929
1930        let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1931        let leftmost_pane_handle = leftmost_pane.downgrade();
1932        let leftmost_frames = SubView::new(
1933            stack_frame_list.focus_handle(cx),
1934            stack_frame_list.clone().into(),
1935            DebuggerPaneItem::Frames,
1936            running_state.clone(),
1937            leftmost_pane_handle.clone(),
1938            cx,
1939        );
1940        let leftmost_breakpoints = SubView::breakpoint_list(
1941            breakpoints.clone(),
1942            running_state.clone(),
1943            leftmost_pane_handle,
1944            cx,
1945        );
1946        leftmost_pane.update(cx, |this, cx| {
1947            this.add_item(Box::new(leftmost_frames), true, false, None, window, cx);
1948            this.add_item(
1949                Box::new(leftmost_breakpoints),
1950                true,
1951                false,
1952                None,
1953                window,
1954                cx,
1955            );
1956            this.activate_item(0, false, false, window, cx);
1957        });
1958
1959        let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1960        let center_pane_handle = center_pane.downgrade();
1961        let center_console = SubView::console(
1962            console.clone(),
1963            running_state.clone(),
1964            center_pane_handle.clone(),
1965            cx,
1966        );
1967        let center_variables = SubView::new(
1968            variable_list.focus_handle(cx),
1969            variable_list.clone().into(),
1970            DebuggerPaneItem::Variables,
1971            running_state.clone(),
1972            center_pane_handle,
1973            cx,
1974        );
1975
1976        center_pane.update(cx, |this, cx| {
1977            this.add_item(Box::new(center_console), true, false, None, window, cx);
1978
1979            this.add_item(Box::new(center_variables), true, false, None, window, cx);
1980            this.activate_item(0, false, false, window, cx);
1981        });
1982
1983        let rightmost_pane = new_debugger_pane(workspace.clone(), project, window, cx);
1984        let rightmost_terminal = SubView::new(
1985            debug_terminal.focus_handle(cx),
1986            debug_terminal.clone().into(),
1987            DebuggerPaneItem::Terminal,
1988            running_state,
1989            rightmost_pane.downgrade(),
1990            cx,
1991        );
1992        rightmost_pane.update(cx, |this, cx| {
1993            this.add_item(Box::new(rightmost_terminal), false, false, None, window, cx);
1994        });
1995
1996        subscriptions.extend(
1997            [&leftmost_pane, &center_pane, &rightmost_pane]
1998                .into_iter()
1999                .map(|entity| {
2000                    (
2001                        entity.entity_id(),
2002                        cx.subscribe_in(entity, window, Self::handle_pane_event),
2003                    )
2004                }),
2005        );
2006
2007        let group_root = workspace::PaneAxis::new(
2008            dock_axis.invert(),
2009            [leftmost_pane, center_pane, rightmost_pane]
2010                .into_iter()
2011                .map(workspace::Member::Pane)
2012                .collect(),
2013        );
2014
2015        Member::Axis(group_root)
2016    }
2017
2018    pub(crate) fn invert_axies(&mut self, cx: &mut App) {
2019        self.dock_axis = self.dock_axis.invert();
2020        self.panes.invert_axies(cx);
2021    }
2022}
2023
2024impl Focusable for RunningState {
2025    fn focus_handle(&self, _: &App) -> FocusHandle {
2026        self.focus_handle.clone()
2027    }
2028}
2029
2030#[cfg(test)]
2031mod tests {
2032    use super::*;
2033    use crate::{
2034        debugger_panel::DebugPanel,
2035        tests::{init_test, init_test_workspace, start_debug_session},
2036    };
2037    use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
2038    use project::{FakeFs, Project};
2039    use serde_json::json;
2040    use util::path;
2041
2042    #[gpui::test]
2043    async fn stale_subview_host_during_tab_drop_does_not_read_updating_source_pane(
2044        executor: BackgroundExecutor,
2045        cx: &mut TestAppContext,
2046    ) {
2047        init_test(cx);
2048
2049        let fs = FakeFs::new(executor);
2050        fs.insert_tree(
2051            path!("/project"),
2052            json!({
2053                "main.rs": "fn main() {}",
2054            }),
2055        )
2056        .await;
2057
2058        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
2059        let workspace = init_test_workspace(&project, cx).await;
2060        let cx = &mut VisualTestContext::from_window(*workspace, cx);
2061
2062        start_debug_session(&workspace, cx, |_| {}).expect("debug session starts");
2063        cx.run_until_parked();
2064
2065        let running_state = workspace
2066            .update(cx, |multi_workspace, _window, cx| {
2067                multi_workspace.workspace().update(cx, |workspace, cx| {
2068                    let debug_panel = workspace.panel::<DebugPanel>(cx).expect("debug panel");
2069                    let active_session = debug_panel
2070                        .read(cx)
2071                        .active_session()
2072                        .expect("active debug session");
2073                    active_session.read(cx).running_state().clone()
2074                })
2075            })
2076            .expect("workspace update succeeds");
2077
2078        let (source_pane, stale_host_pane) = running_state.read_with(cx, |running_state, _| {
2079            let panes = running_state.panes.panes();
2080            let mut panes = panes.into_iter();
2081            let source_pane = panes.next().expect("source pane").clone();
2082            let stale_host_pane = panes.next().expect("stale host pane").clone();
2083            (source_pane, stale_host_pane)
2084        });
2085
2086        let dragged_tab = {
2087            let source_pane_entity = source_pane.clone();
2088            source_pane.read_with(cx, |source_pane, _| {
2089                let item = source_pane
2090                    .item_for_index(0)
2091                    .expect("source pane contains debugger subview")
2092                    .boxed_clone();
2093                DraggedTab {
2094                    pane: source_pane_entity,
2095                    item,
2096                    ix: 0,
2097                    detail: 0,
2098                    is_active: true,
2099                }
2100            })
2101        };
2102
2103        let active_subview = source_pane.read_with(cx, |source_pane, _| {
2104            source_pane
2105                .active_item()
2106                .and_then(|item| item.downcast::<SubView>())
2107                .expect("active item is a debugger subview")
2108        });
2109        active_subview.update(cx, |subview, _| {
2110            subview.set_host_pane(stale_host_pane.downgrade());
2111        });
2112
2113        source_pane.update_in(cx, |source_pane, window, cx| {
2114            source_pane.handle_tab_drop(
2115                &dragged_tab,
2116                source_pane.active_item_index(),
2117                true,
2118                window,
2119                cx,
2120            );
2121        });
2122    }
2123}
2124
Served at tenant.openagents/omega Member data and write actions are omitted.