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