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