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