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 pane_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 pane_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 pane_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.pane_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.pane_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 .child(
356 h_flex()
357 .w_full()
358 .gap_1()
359 .h(Tab::container_height(cx))
360 .drag_over::<DraggedTab>(|bar, _, _, cx| {
361 bar.bg(cx.theme().colors().drop_target_background)
362 })
363 .on_drop(cx.listener(
364 move |this, dragged_tab: &DraggedTab, window, cx| {
365 this.drag_split_direction = None;
366 this.handle_tab_drop(dragged_tab, this.items_len(), window, cx)
367 },
368 ))
369 .children(pane.items().enumerate().map(|(ix, item)| {
370 let selected = active_pane_item
371 .as_ref()
372 .map_or(false, |active| active.item_id() == item.item_id());
373 let deemphasized = !pane.has_focus(window, cx);
374 let item_ = item.boxed_clone();
375 div()
376 .id(SharedString::from(format!(
377 "debugger_tab_{}",
378 item.item_id().as_u64()
379 )))
380 .p_1()
381 .rounded_md()
382 .cursor_pointer()
383 .map(|this| {
384 let theme = cx.theme();
385 if selected {
386 let color = theme.colors().tab_active_background;
387 let color = if deemphasized {
388 color.opacity(0.5)
389 } else {
390 color
391 };
392 this.bg(color)
393 } else {
394 let hover_color = theme.colors().element_hover;
395 this.hover(|style| style.bg(hover_color))
396 }
397 })
398 .on_click(cx.listener(move |this, _, window, cx| {
399 let index = this.index_for_item(&*item_);
400 if let Some(index) = index {
401 this.activate_item(index, true, true, window, cx);
402 }
403 }))
404 .child(item.tab_content(
405 TabContentParams {
406 selected,
407 deemphasized,
408 ..Default::default()
409 },
410 window,
411 cx,
412 ))
413 .on_drop(cx.listener(
414 move |this, dragged_tab: &DraggedTab, window, cx| {
415 this.drag_split_direction = None;
416 this.handle_tab_drop(dragged_tab, ix, window, cx)
417 },
418 ))
419 .on_drag(
420 DraggedTab {
421 item: item.boxed_clone(),
422 pane: cx.entity().clone(),
423 detail: 0,
424 is_active: selected,
425 ix,
426 },
427 |tab, _, _, cx| cx.new(|_| tab.clone()),
428 )
429 })),
430 )
431 .child({
432 let zoomed = pane.is_zoomed();
433 div()
434 .visible_on_hover(pane_group_id)
435 .when(is_hovered, |this| this.visible())
436 .child(
437 IconButton::new(
438 SharedString::from(format!(
439 "debug-toggle-zoom-{}",
440 cx.entity_id()
441 )),
442 if zoomed {
443 IconName::Minimize
444 } else {
445 IconName::Maximize
446 },
447 )
448 .icon_size(IconSize::XSmall)
449 .on_click(cx.listener(move |pane, _, window, cx| {
450 pane.toggle_zoom(&workspace::ToggleZoom, window, cx);
451 }))
452 .tooltip({
453 let focus_handle = focus_handle.clone();
454 move |window, cx| {
455 let zoomed_text =
456 if zoomed { "Zoom Out" } else { "Zoom In" };
457 Tooltip::for_action_in(
458 zoomed_text,
459 &workspace::ToggleZoom,
460 &focus_handle,
461 window,
462 cx,
463 )
464 }
465 }),
466 )
467 })
468 .into_any_element()
469 }
470 });
471 pane
472 });
473
474 ret
475}
476
477pub struct DebugTerminal {
478 pub terminal: Option<Entity<TerminalView>>,
479 focus_handle: FocusHandle,
480}
481
482impl DebugTerminal {
483 fn empty(cx: &mut Context<Self>) -> Self {
484 Self {
485 terminal: None,
486 focus_handle: cx.focus_handle(),
487 }
488 }
489}
490
491impl gpui::Render for DebugTerminal {
492 fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
493 if let Some(terminal) = self.terminal.clone() {
494 terminal.into_any_element()
495 } else {
496 div().track_focus(&self.focus_handle).into_any_element()
497 }
498 }
499}
500impl Focusable for DebugTerminal {
501 fn focus_handle(&self, cx: &App) -> FocusHandle {
502 if let Some(terminal) = self.terminal.as_ref() {
503 return terminal.focus_handle(cx);
504 } else {
505 self.focus_handle.clone()
506 }
507 }
508}
509
510impl RunningState {
511 pub fn new(
512 session: Entity<Session>,
513 project: Entity<Project>,
514 workspace: WeakEntity<Workspace>,
515 serialized_pane_layout: Option<SerializedLayout>,
516 dock_axis: Axis,
517 window: &mut Window,
518 cx: &mut Context<Self>,
519 ) -> Self {
520 let focus_handle = cx.focus_handle();
521 let session_id = session.read(cx).session_id();
522 let weak_state = cx.weak_entity();
523 let stack_frame_list = cx.new(|cx| {
524 StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx)
525 });
526
527 let debug_terminal = cx.new(DebugTerminal::empty);
528
529 let variable_list =
530 cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx));
531
532 let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
533
534 let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
535
536 let console = cx.new(|cx| {
537 Console::new(
538 session.clone(),
539 stack_frame_list.clone(),
540 variable_list.clone(),
541 window,
542 cx,
543 )
544 });
545
546 let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
547
548 let _subscriptions = vec![
549 cx.observe(&module_list, |_, _, cx| cx.notify()),
550 cx.subscribe_in(&session, window, |this, _, event, window, cx| {
551 match event {
552 SessionEvent::Stopped(thread_id) => {
553 this.workspace
554 .update(cx, |workspace, cx| {
555 workspace.open_panel::<crate::DebugPanel>(window, cx);
556 })
557 .log_err();
558
559 if let Some(thread_id) = thread_id {
560 this.select_thread(*thread_id, window, cx);
561 }
562 }
563 SessionEvent::Threads => {
564 let threads = this.session.update(cx, |this, cx| this.threads(cx));
565 this.select_current_thread(&threads, window, cx);
566 }
567 SessionEvent::CapabilitiesLoaded => {
568 let capabilities = this.capabilities(cx);
569 if !capabilities.supports_modules_request.unwrap_or(false) {
570 this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
571 }
572 if !capabilities
573 .supports_loaded_sources_request
574 .unwrap_or(false)
575 {
576 this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
577 }
578 }
579 SessionEvent::RunInTerminal { request, sender } => this
580 .handle_run_in_terminal(request, sender.clone(), window, cx)
581 .detach_and_log_err(cx),
582
583 _ => {}
584 }
585 cx.notify()
586 }),
587 cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
588 this.serialize_layout(window, cx);
589 }),
590 ];
591
592 let mut pane_close_subscriptions = HashMap::default();
593 let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
594 persistence::deserialize_pane_layout(
595 serialized_layout.panes,
596 dock_axis != serialized_layout.dock_axis,
597 &workspace,
598 &project,
599 &stack_frame_list,
600 &variable_list,
601 &module_list,
602 &console,
603 &breakpoint_list,
604 &loaded_source_list,
605 &debug_terminal,
606 &mut pane_close_subscriptions,
607 window,
608 cx,
609 )
610 }) {
611 workspace::PaneGroup::with_root(root)
612 } else {
613 pane_close_subscriptions.clear();
614
615 let root = Self::default_pane_layout(
616 project,
617 &workspace,
618 &stack_frame_list,
619 &variable_list,
620 &module_list,
621 &loaded_source_list,
622 &console,
623 &breakpoint_list,
624 dock_axis,
625 &mut pane_close_subscriptions,
626 window,
627 cx,
628 );
629
630 workspace::PaneGroup::with_root(root)
631 };
632
633 Self {
634 session,
635 workspace,
636 focus_handle,
637 variable_list,
638 _subscriptions,
639 thread_id: None,
640 _remote_id: None,
641 stack_frame_list,
642 session_id,
643 panes,
644 active_pane: None,
645 module_list,
646 console,
647 breakpoint_list,
648 loaded_sources_list: loaded_source_list,
649 pane_close_subscriptions,
650 debug_terminal,
651 dock_axis,
652 _schedule_serialize: None,
653 }
654 }
655
656 pub(crate) fn remove_pane_item(
657 &mut self,
658 item_kind: DebuggerPaneItem,
659 window: &mut Window,
660 cx: &mut Context<Self>,
661 ) {
662 if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
663 Some(pane).zip(
664 pane.read(cx)
665 .items()
666 .find(|item| {
667 item.act_as::<SubView>(cx)
668 .is_some_and(|view| view.read(cx).kind == item_kind)
669 })
670 .map(|item| item.item_id()),
671 )
672 }) {
673 pane.update(cx, |pane, cx| {
674 pane.remove_item(item_id, false, true, window, cx)
675 })
676 }
677 }
678
679 pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
680 self.panes.pane_at_pixel_position(position).is_some()
681 }
682
683 pub(crate) fn resolve_scenario(
684 &self,
685 scenario: DebugScenario,
686 task_context: TaskContext,
687 buffer: Option<Entity<Buffer>>,
688 worktree_id: Option<WorktreeId>,
689 window: &Window,
690 cx: &mut Context<Self>,
691 ) -> Task<Result<DebugTaskDefinition>> {
692 let Some(workspace) = self.workspace.upgrade() else {
693 return Task::ready(Err(anyhow!("no workspace")));
694 };
695 let project = workspace.read(cx).project().clone();
696 let dap_store = project.read(cx).dap_store().downgrade();
697 let task_store = project.read(cx).task_store().downgrade();
698 let weak_project = project.downgrade();
699 let weak_workspace = workspace.downgrade();
700 let is_local = project.read(cx).is_local();
701 cx.spawn_in(window, async move |this, cx| {
702 let DebugScenario {
703 adapter,
704 label,
705 build,
706 request,
707 initialize_args,
708 tcp_connection,
709 stop_on_entry,
710 } = scenario;
711 let build_output = if let Some(build) = build {
712 let (task, locator_name) = match build {
713 BuildTaskDefinition::Template {
714 task_template,
715 locator_name,
716 } => (task_template, locator_name),
717 BuildTaskDefinition::ByName(ref label) => {
718 let Some(task) = task_store.update(cx, |this, cx| {
719 this.task_inventory().and_then(|inventory| {
720 inventory.read(cx).task_template_by_label(
721 buffer,
722 worktree_id,
723 &label,
724 cx,
725 )
726 })
727 })?
728 else {
729 anyhow::bail!("Couldn't find task template for {:?}", build)
730 };
731 (task, None)
732 }
733 };
734 let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
735 anyhow::bail!("Could not resolve task variables within a debug scenario");
736 };
737
738 let locator_name = if let Some(locator_name) = locator_name {
739 debug_assert!(request.is_none());
740 Some(locator_name)
741 } else if request.is_none() {
742 dap_store
743 .update(cx, |this, cx| {
744 this.debug_scenario_for_build_task(
745 task.original_task().clone(),
746 adapter.clone().into(),
747 task.display_label().to_owned().into(),
748 cx,
749 )
750 .and_then(|scenario| {
751 match scenario.build {
752 Some(BuildTaskDefinition::Template {
753 locator_name, ..
754 }) => locator_name,
755 _ => None,
756 }
757 })
758 })
759 .ok()
760 .flatten()
761 } else {
762 None
763 };
764
765 let builder = ShellBuilder::new(is_local, &task.resolved.shell);
766 let command_label = builder.command_label(&task.resolved.command_label);
767 let (command, args) =
768 builder.build(task.resolved.command.clone(), &task.resolved.args);
769
770 let task_with_shell = SpawnInTerminal {
771 command_label,
772 command,
773 args,
774 ..task.resolved.clone()
775 };
776 let terminal = project
777 .update_in(cx, |project, window, cx| {
778 project.create_terminal(
779 TerminalKind::Task(task_with_shell.clone()),
780 window.window_handle(),
781 cx,
782 )
783 })?
784 .await?;
785
786 let terminal_view = cx.new_window_entity(|window, cx| {
787 TerminalView::new(
788 terminal.clone(),
789 weak_workspace,
790 None,
791 weak_project,
792 false,
793 window,
794 cx,
795 )
796 })?;
797
798 this.update_in(cx, |this, window, cx| {
799 this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
800 this.debug_terminal.update(cx, |debug_terminal, cx| {
801 debug_terminal.terminal = Some(terminal_view);
802 cx.notify();
803 });
804 })?;
805
806 let exit_status = terminal
807 .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
808 .await
809 .ok_or_else(|| anyhow!("Failed to wait for completed task"))?;
810
811 if !exit_status.success() {
812 anyhow::bail!("Build failed");
813 }
814 Some((task.resolved.clone(), locator_name))
815 } else {
816 None
817 };
818 let request = if let Some(request) = request {
819 request
820 } else if let Some((task, locator_name)) = build_output {
821 let locator_name = locator_name
822 .ok_or_else(|| anyhow!("Could not find a valid locator for a build task"))?;
823 dap_store
824 .update(cx, |this, cx| {
825 this.run_debug_locator(&locator_name, task, cx)
826 })?
827 .await?
828 } else {
829 return Err(anyhow!("No request or build provided"));
830 };
831 let request = match request {
832 dap::DebugRequest::Launch(launch_request) => {
833 let cwd = match launch_request.cwd.as_deref().and_then(|path| path.to_str()) {
834 Some(cwd) => {
835 let substituted_cwd = substitute_variables_in_str(&cwd, &task_context)
836 .ok_or_else(|| anyhow!("Failed to substitute variables in cwd"))?;
837 Some(PathBuf::from(substituted_cwd))
838 }
839 None => None,
840 };
841
842 let env = substitute_variables_in_map(
843 &launch_request.env.into_iter().collect(),
844 &task_context,
845 )
846 .ok_or_else(|| anyhow!("Failed to substitute variables in env"))?
847 .into_iter()
848 .collect();
849 let new_launch_request = LaunchRequest {
850 program: substitute_variables_in_str(
851 &launch_request.program,
852 &task_context,
853 )
854 .ok_or_else(|| anyhow!("Failed to substitute variables in program"))?,
855 args: launch_request
856 .args
857 .into_iter()
858 .map(|arg| substitute_variables_in_str(&arg, &task_context))
859 .collect::<Option<Vec<_>>>()
860 .ok_or_else(|| anyhow!("Failed to substitute variables in args"))?,
861 cwd,
862 env,
863 };
864
865 dap::DebugRequest::Launch(new_launch_request)
866 }
867 request @ dap::DebugRequest::Attach(_) => request, // todo(debugger): We should check that process_id is valid and if not show the modal
868 };
869 Ok(DebugTaskDefinition {
870 label,
871 adapter: DebugAdapterName(adapter),
872 request,
873 initialize_args,
874 stop_on_entry,
875 tcp_connection,
876 })
877 })
878 }
879
880 fn handle_run_in_terminal(
881 &self,
882 request: &RunInTerminalRequestArguments,
883 mut sender: mpsc::Sender<Result<u32>>,
884 window: &mut Window,
885 cx: &mut Context<Self>,
886 ) -> Task<Result<()>> {
887 let running = cx.entity();
888 let Ok(project) = self
889 .workspace
890 .update(cx, |workspace, _| workspace.project().clone())
891 else {
892 return Task::ready(Err(anyhow!("no workspace")));
893 };
894 let session = self.session.read(cx);
895
896 let cwd = Some(&request.cwd)
897 .filter(|cwd| cwd.len() > 0)
898 .map(PathBuf::from)
899 .or_else(|| session.binary().cwd.clone());
900
901 let mut args = request.args.clone();
902
903 // Handle special case for NodeJS debug adapter
904 // If only the Node binary path is provided, we set the command to None
905 // This prevents the NodeJS REPL from appearing, which is not the desired behavior
906 // The expected usage is for users to provide their own Node command, e.g., `node test.js`
907 // This allows the NodeJS debug client to attach correctly
908 let command = if args.len() > 1 {
909 Some(args.remove(0))
910 } else {
911 None
912 };
913
914 let mut envs: HashMap<String, String> = Default::default();
915 if let Some(Value::Object(env)) = &request.env {
916 for (key, value) in env {
917 let value_str = match (key.as_str(), value) {
918 (_, Value::String(value)) => value,
919 _ => continue,
920 };
921
922 envs.insert(key.clone(), value_str.clone());
923 }
924 }
925
926 let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
927 let kind = if let Some(command) = command {
928 let title = request.title.clone().unwrap_or(command.clone());
929 TerminalKind::Task(task::SpawnInTerminal {
930 id: task::TaskId("debug".to_string()),
931 full_label: title.clone(),
932 label: title.clone(),
933 command: command.clone(),
934 args,
935 command_label: title.clone(),
936 cwd,
937 env: envs,
938 use_new_terminal: true,
939 allow_concurrent_runs: true,
940 reveal: task::RevealStrategy::NoFocus,
941 reveal_target: task::RevealTarget::Dock,
942 hide: task::HideStrategy::Never,
943 shell,
944 show_summary: false,
945 show_command: false,
946 show_rerun: false,
947 })
948 } else {
949 TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
950 };
951
952 let workspace = self.workspace.clone();
953 let weak_project = project.downgrade();
954
955 let terminal_task = project.update(cx, |project, cx| {
956 project.create_terminal(kind, window.window_handle(), cx)
957 });
958 let terminal_task = cx.spawn_in(window, async move |_, cx| {
959 let terminal = terminal_task.await?;
960
961 let terminal_view = cx.new_window_entity(|window, cx| {
962 TerminalView::new(
963 terminal.clone(),
964 workspace,
965 None,
966 weak_project,
967 false,
968 window,
969 cx,
970 )
971 })?;
972
973 running.update_in(cx, |running, window, cx| {
974 running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
975 running.debug_terminal.update(cx, |debug_terminal, cx| {
976 debug_terminal.terminal = Some(terminal_view);
977 cx.notify();
978 });
979 })?;
980
981 terminal.read_with(cx, |terminal, _| {
982 terminal
983 .pty_info
984 .pid()
985 .map(|pid| pid.as_u32())
986 .ok_or_else(|| anyhow!("Terminal was spawned but PID was not available"))
987 })?
988 });
989
990 cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
991 }
992
993 fn create_sub_view(
994 &self,
995 item_kind: DebuggerPaneItem,
996 _pane: &Entity<Pane>,
997 cx: &mut Context<Self>,
998 ) -> Box<dyn ItemHandle> {
999 match item_kind {
1000 DebuggerPaneItem::Console => {
1001 let weak_console = self.console.clone().downgrade();
1002
1003 Box::new(SubView::new(
1004 self.console.focus_handle(cx),
1005 self.console.clone().into(),
1006 item_kind,
1007 Some(Box::new(move |cx| {
1008 weak_console
1009 .read_with(cx, |console, cx| console.show_indicator(cx))
1010 .unwrap_or_default()
1011 })),
1012 cx,
1013 ))
1014 }
1015 DebuggerPaneItem::Variables => Box::new(SubView::new(
1016 self.variable_list.focus_handle(cx),
1017 self.variable_list.clone().into(),
1018 item_kind,
1019 None,
1020 cx,
1021 )),
1022 DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
1023 self.breakpoint_list.focus_handle(cx),
1024 self.breakpoint_list.clone().into(),
1025 item_kind,
1026 None,
1027 cx,
1028 )),
1029 DebuggerPaneItem::Frames => Box::new(SubView::new(
1030 self.stack_frame_list.focus_handle(cx),
1031 self.stack_frame_list.clone().into(),
1032 item_kind,
1033 None,
1034 cx,
1035 )),
1036 DebuggerPaneItem::Modules => Box::new(SubView::new(
1037 self.module_list.focus_handle(cx),
1038 self.module_list.clone().into(),
1039 item_kind,
1040 None,
1041 cx,
1042 )),
1043 DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
1044 self.loaded_sources_list.focus_handle(cx),
1045 self.loaded_sources_list.clone().into(),
1046 item_kind,
1047 None,
1048 cx,
1049 )),
1050 DebuggerPaneItem::Terminal => Box::new(SubView::new(
1051 self.debug_terminal.focus_handle(cx),
1052 self.debug_terminal.clone().into(),
1053 item_kind,
1054 None,
1055 cx,
1056 )),
1057 }
1058 }
1059
1060 pub(crate) fn ensure_pane_item(
1061 &mut self,
1062 item_kind: DebuggerPaneItem,
1063 window: &mut Window,
1064 cx: &mut Context<Self>,
1065 ) {
1066 if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
1067 return;
1068 };
1069 let pane = self.panes.last_pane();
1070 let sub_view = self.create_sub_view(item_kind, &pane, cx);
1071
1072 pane.update(cx, |pane, cx| {
1073 pane.add_item_inner(sub_view, false, false, false, None, window, cx);
1074 })
1075 }
1076
1077 pub(crate) fn add_pane_item(
1078 &mut self,
1079 item_kind: DebuggerPaneItem,
1080 position: Point<Pixels>,
1081 window: &mut Window,
1082 cx: &mut Context<Self>,
1083 ) {
1084 debug_assert!(
1085 item_kind.is_supported(self.session.read(cx).capabilities()),
1086 "We should only allow adding supported item kinds"
1087 );
1088
1089 if let Some(pane) = self.panes.pane_at_pixel_position(position) {
1090 let sub_view = self.create_sub_view(item_kind, pane, cx);
1091
1092 pane.update(cx, |pane, cx| {
1093 pane.add_item(sub_view, false, false, None, window, cx);
1094 })
1095 }
1096 }
1097
1098 pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
1099 let caps = self.session.read(cx).capabilities();
1100 let mut pane_item_status = IndexMap::from_iter(
1101 DebuggerPaneItem::all()
1102 .iter()
1103 .filter(|kind| kind.is_supported(&caps))
1104 .map(|kind| (*kind, false)),
1105 );
1106 self.panes.panes().iter().for_each(|pane| {
1107 pane.read(cx)
1108 .items()
1109 .filter_map(|item| item.act_as::<SubView>(cx))
1110 .for_each(|view| {
1111 pane_item_status.insert(view.read(cx).kind, true);
1112 });
1113 });
1114
1115 pane_item_status
1116 }
1117
1118 pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1119 if self._schedule_serialize.is_none() {
1120 self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
1121 cx.background_executor()
1122 .timer(Duration::from_millis(100))
1123 .await;
1124
1125 let Some((adapter_name, pane_layout)) = this
1126 .read_with(cx, |this, cx| {
1127 let adapter_name = this.session.read(cx).adapter();
1128 (
1129 adapter_name,
1130 persistence::build_serialized_layout(
1131 &this.panes.root,
1132 this.dock_axis,
1133 cx,
1134 ),
1135 )
1136 })
1137 .ok()
1138 else {
1139 return;
1140 };
1141
1142 persistence::serialize_pane_layout(adapter_name, pane_layout)
1143 .await
1144 .log_err();
1145
1146 this.update(cx, |this, _| {
1147 this._schedule_serialize.take();
1148 })
1149 .ok();
1150 }));
1151 }
1152 }
1153
1154 pub(crate) fn handle_pane_event(
1155 this: &mut RunningState,
1156 source_pane: &Entity<Pane>,
1157 event: &Event,
1158 window: &mut Window,
1159 cx: &mut Context<RunningState>,
1160 ) {
1161 this.serialize_layout(window, cx);
1162 match event {
1163 Event::Remove { .. } => {
1164 let _did_find_pane = this.panes.remove(&source_pane).is_ok();
1165 debug_assert!(_did_find_pane);
1166 cx.notify();
1167 }
1168 Event::Focus => {
1169 this.active_pane = Some(source_pane.clone());
1170 }
1171 Event::ZoomIn => {
1172 source_pane.update(cx, |pane, cx| {
1173 pane.set_zoomed(true, cx);
1174 });
1175 cx.notify();
1176 }
1177 Event::ZoomOut => {
1178 source_pane.update(cx, |pane, cx| {
1179 pane.set_zoomed(false, cx);
1180 });
1181 cx.notify();
1182 }
1183 _ => {}
1184 }
1185 }
1186
1187 pub(crate) fn activate_pane_in_direction(
1188 &mut self,
1189 direction: SplitDirection,
1190 window: &mut Window,
1191 cx: &mut Context<Self>,
1192 ) {
1193 if let Some(pane) = self
1194 .active_pane
1195 .as_ref()
1196 .and_then(|pane| self.panes.find_pane_in_direction(pane, direction, cx))
1197 {
1198 window.focus(&pane.focus_handle(cx));
1199 } else {
1200 self.workspace
1201 .update(cx, |workspace, cx| {
1202 workspace.activate_pane_in_direction(direction, window, cx)
1203 })
1204 .ok();
1205 }
1206 }
1207
1208 pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
1209 if self.thread_id.is_some() {
1210 self.stack_frame_list
1211 .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx));
1212 }
1213 }
1214
1215 pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
1216 self.variable_list.read(cx).has_open_context_menu()
1217 }
1218
1219 pub fn session(&self) -> &Entity<Session> {
1220 &self.session
1221 }
1222
1223 pub fn session_id(&self) -> SessionId {
1224 self.session_id
1225 }
1226
1227 pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
1228 self.stack_frame_list.read(cx).selected_stack_frame_id()
1229 }
1230
1231 #[cfg(test)]
1232 pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
1233 &self.stack_frame_list
1234 }
1235
1236 #[cfg(test)]
1237 pub fn console(&self) -> &Entity<Console> {
1238 &self.console
1239 }
1240
1241 #[cfg(test)]
1242 pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
1243 &self.module_list
1244 }
1245
1246 pub(crate) fn activate_item(&self, item: DebuggerPaneItem, window: &mut Window, cx: &mut App) {
1247 let (variable_list_position, pane) = self
1248 .panes
1249 .panes()
1250 .into_iter()
1251 .find_map(|pane| {
1252 pane.read(cx)
1253 .items_of_type::<SubView>()
1254 .position(|view| view.read(cx).view_kind() == item)
1255 .map(|view| (view, pane))
1256 })
1257 .unwrap();
1258 pane.update(cx, |this, cx| {
1259 this.activate_item(variable_list_position, true, true, window, cx);
1260 })
1261 }
1262
1263 #[cfg(test)]
1264 pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
1265 &self.variable_list
1266 }
1267
1268 #[cfg(test)]
1269 pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
1270 persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
1271 }
1272
1273 pub fn capabilities(&self, cx: &App) -> Capabilities {
1274 self.session().read(cx).capabilities().clone()
1275 }
1276
1277 pub fn select_current_thread(
1278 &mut self,
1279 threads: &Vec<(Thread, ThreadStatus)>,
1280 window: &mut Window,
1281 cx: &mut Context<Self>,
1282 ) {
1283 let selected_thread = self
1284 .thread_id
1285 .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
1286 .or_else(|| threads.first());
1287
1288 let Some((selected_thread, _)) = selected_thread else {
1289 return;
1290 };
1291
1292 if Some(ThreadId(selected_thread.id)) != self.thread_id {
1293 self.select_thread(ThreadId(selected_thread.id), window, cx);
1294 }
1295 }
1296
1297 pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
1298 self.thread_id
1299 }
1300
1301 pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
1302 self.thread_id
1303 .map(|id| self.session().read(cx).thread_status(id))
1304 }
1305
1306 fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
1307 if self.thread_id.is_some_and(|id| id == thread_id) {
1308 return;
1309 }
1310
1311 self.thread_id = Some(thread_id);
1312
1313 self.stack_frame_list
1314 .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
1315 }
1316
1317 pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
1318 let Some(thread_id) = self.thread_id else {
1319 return;
1320 };
1321
1322 self.session().update(cx, |state, cx| {
1323 state.continue_thread(thread_id, cx);
1324 });
1325 }
1326
1327 pub fn step_over(&mut self, cx: &mut Context<Self>) {
1328 let Some(thread_id) = self.thread_id else {
1329 return;
1330 };
1331
1332 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1333
1334 self.session().update(cx, |state, cx| {
1335 state.step_over(thread_id, granularity, cx);
1336 });
1337 }
1338
1339 pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
1340 let Some(thread_id) = self.thread_id else {
1341 return;
1342 };
1343
1344 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1345
1346 self.session().update(cx, |state, cx| {
1347 state.step_in(thread_id, granularity, cx);
1348 });
1349 }
1350
1351 pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
1352 let Some(thread_id) = self.thread_id else {
1353 return;
1354 };
1355
1356 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1357
1358 self.session().update(cx, |state, cx| {
1359 state.step_out(thread_id, granularity, cx);
1360 });
1361 }
1362
1363 pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
1364 let Some(thread_id) = self.thread_id else {
1365 return;
1366 };
1367
1368 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1369
1370 self.session().update(cx, |state, cx| {
1371 state.step_back(thread_id, granularity, cx);
1372 });
1373 }
1374
1375 pub fn restart_session(&self, cx: &mut Context<Self>) {
1376 self.session().update(cx, |state, cx| {
1377 state.restart(None, cx);
1378 });
1379 }
1380
1381 pub fn pause_thread(&self, cx: &mut Context<Self>) {
1382 let Some(thread_id) = self.thread_id else {
1383 return;
1384 };
1385
1386 self.session().update(cx, |state, cx| {
1387 state.pause_thread(thread_id, cx);
1388 });
1389 }
1390
1391 pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
1392 self.workspace
1393 .update(cx, |workspace, cx| {
1394 workspace
1395 .project()
1396 .read(cx)
1397 .breakpoint_store()
1398 .update(cx, |store, cx| {
1399 store.remove_active_position(Some(self.session_id), cx)
1400 })
1401 })
1402 .log_err();
1403
1404 self.session.update(cx, |session, cx| {
1405 session.shutdown(cx).detach();
1406 })
1407 }
1408
1409 pub fn stop_thread(&self, cx: &mut Context<Self>) {
1410 let Some(thread_id) = self.thread_id else {
1411 return;
1412 };
1413
1414 self.workspace
1415 .update(cx, |workspace, cx| {
1416 workspace
1417 .project()
1418 .read(cx)
1419 .breakpoint_store()
1420 .update(cx, |store, cx| {
1421 store.remove_active_position(Some(self.session_id), cx)
1422 })
1423 })
1424 .log_err();
1425
1426 self.session().update(cx, |state, cx| {
1427 state.terminate_threads(Some(vec![thread_id; 1]), cx);
1428 });
1429 }
1430
1431 pub fn detach_client(&self, cx: &mut Context<Self>) {
1432 self.session().update(cx, |state, cx| {
1433 state.disconnect_client(cx);
1434 });
1435 }
1436
1437 pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1438 self.session.update(cx, |session, cx| {
1439 session.toggle_ignore_breakpoints(cx).detach();
1440 });
1441 }
1442
1443 pub(crate) fn thread_dropdown(
1444 &self,
1445 window: &mut Window,
1446 cx: &mut Context<'_, RunningState>,
1447 ) -> DropdownMenu {
1448 let state = cx.entity();
1449 let session_terminated = self.session.read(cx).is_terminated();
1450 let threads = self.session.update(cx, |this, cx| this.threads(cx));
1451 let selected_thread_name = threads
1452 .iter()
1453 .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
1454 .map(|(thread, _)| thread.name.clone())
1455 .unwrap_or("Threads".to_owned());
1456 DropdownMenu::new(
1457 ("thread-list", self.session_id.0),
1458 selected_thread_name,
1459 ContextMenu::build_eager(window, cx, move |mut this, _, _| {
1460 for (thread, _) in threads {
1461 let state = state.clone();
1462 let thread_id = thread.id;
1463 this = this.entry(thread.name, None, move |window, cx| {
1464 state.update(cx, |state, cx| {
1465 state.select_thread(ThreadId(thread_id), window, cx);
1466 });
1467 });
1468 }
1469 this
1470 }),
1471 )
1472 .disabled(session_terminated)
1473 }
1474
1475 fn default_pane_layout(
1476 project: Entity<Project>,
1477 workspace: &WeakEntity<Workspace>,
1478 stack_frame_list: &Entity<StackFrameList>,
1479 variable_list: &Entity<VariableList>,
1480 module_list: &Entity<ModuleList>,
1481 loaded_source_list: &Entity<LoadedSourceList>,
1482 console: &Entity<Console>,
1483 breakpoints: &Entity<BreakpointList>,
1484 dock_axis: Axis,
1485 subscriptions: &mut HashMap<EntityId, Subscription>,
1486 window: &mut Window,
1487 cx: &mut Context<'_, RunningState>,
1488 ) -> Member {
1489 let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1490 leftmost_pane.update(cx, |this, cx| {
1491 this.add_item(
1492 Box::new(SubView::new(
1493 this.focus_handle(cx),
1494 stack_frame_list.clone().into(),
1495 DebuggerPaneItem::Frames,
1496 None,
1497 cx,
1498 )),
1499 true,
1500 false,
1501 None,
1502 window,
1503 cx,
1504 );
1505 this.add_item(
1506 Box::new(SubView::new(
1507 breakpoints.focus_handle(cx),
1508 breakpoints.clone().into(),
1509 DebuggerPaneItem::BreakpointList,
1510 None,
1511 cx,
1512 )),
1513 true,
1514 false,
1515 None,
1516 window,
1517 cx,
1518 );
1519 this.activate_item(0, false, false, window, cx);
1520 });
1521 let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1522
1523 center_pane.update(cx, |this, cx| {
1524 this.add_item(
1525 Box::new(SubView::new(
1526 variable_list.focus_handle(cx),
1527 variable_list.clone().into(),
1528 DebuggerPaneItem::Variables,
1529 None,
1530 cx,
1531 )),
1532 true,
1533 false,
1534 None,
1535 window,
1536 cx,
1537 );
1538 this.add_item(
1539 Box::new(SubView::new(
1540 module_list.focus_handle(cx),
1541 module_list.clone().into(),
1542 DebuggerPaneItem::Modules,
1543 None,
1544 cx,
1545 )),
1546 false,
1547 false,
1548 None,
1549 window,
1550 cx,
1551 );
1552
1553 this.add_item(
1554 Box::new(SubView::new(
1555 loaded_source_list.focus_handle(cx),
1556 loaded_source_list.clone().into(),
1557 DebuggerPaneItem::LoadedSources,
1558 None,
1559 cx,
1560 )),
1561 false,
1562 false,
1563 None,
1564 window,
1565 cx,
1566 );
1567 this.activate_item(0, false, false, window, cx);
1568 });
1569
1570 let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1571 rightmost_pane.update(cx, |this, cx| {
1572 let weak_console = console.downgrade();
1573 this.add_item(
1574 Box::new(SubView::new(
1575 this.focus_handle(cx),
1576 console.clone().into(),
1577 DebuggerPaneItem::Console,
1578 Some(Box::new(move |cx| {
1579 weak_console
1580 .read_with(cx, |console, cx| console.show_indicator(cx))
1581 .unwrap_or_default()
1582 })),
1583 cx,
1584 )),
1585 true,
1586 false,
1587 None,
1588 window,
1589 cx,
1590 );
1591 });
1592
1593 subscriptions.extend(
1594 [&leftmost_pane, ¢er_pane, &rightmost_pane]
1595 .into_iter()
1596 .map(|entity| {
1597 (
1598 entity.entity_id(),
1599 cx.subscribe_in(entity, window, Self::handle_pane_event),
1600 )
1601 }),
1602 );
1603
1604 let group_root = workspace::PaneAxis::new(
1605 dock_axis.invert(),
1606 [leftmost_pane, center_pane, rightmost_pane]
1607 .into_iter()
1608 .map(workspace::Member::Pane)
1609 .collect(),
1610 );
1611
1612 Member::Axis(group_root)
1613 }
1614
1615 pub(crate) fn invert_axies(&mut self) {
1616 self.dock_axis = self.dock_axis.invert();
1617 self.panes.invert_axies();
1618 }
1619}
1620
1621impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1622
1623impl Focusable for RunningState {
1624 fn focus_handle(&self, _: &App) -> FocusHandle {
1625 self.focus_handle.clone()
1626 }
1627}