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, sync::Arc, time::Duration};
9
10use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
11
12use super::DebugPanelItemEvent;
13use breakpoint_list::BreakpointList;
14use collections::{HashMap, IndexMap};
15use console::Console;
16use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
17use gpui::{
18 Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
19 NoAction, Pixels, Point, Subscription, Task, WeakEntity,
20};
21use loaded_source_list::LoadedSourceList;
22use module_list::ModuleList;
23use project::{
24 Project,
25 debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
26};
27use rpc::proto::ViewId;
28use settings::Settings;
29use stack_frame_list::StackFrameList;
30use terminal_view::TerminalView;
31use ui::{
32 ActiveTheme, AnyElement, App, Context, ContextMenu, DropdownMenu, FluentBuilder,
33 InteractiveElement, IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString,
34 StatefulInteractiveElement, Styled, Tab, Window, div, h_flex, v_flex,
35};
36use util::ResultExt;
37use variable_list::VariableList;
38use workspace::{
39 ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, Workspace,
40 item::TabContentParams, move_item, pane::Event,
41};
42
43pub struct RunningState {
44 session: Entity<Session>,
45 thread_id: Option<ThreadId>,
46 focus_handle: FocusHandle,
47 _remote_id: Option<ViewId>,
48 workspace: WeakEntity<Workspace>,
49 session_id: SessionId,
50 variable_list: Entity<variable_list::VariableList>,
51 _subscriptions: Vec<Subscription>,
52 stack_frame_list: Entity<stack_frame_list::StackFrameList>,
53 loaded_sources_list: Entity<LoadedSourceList>,
54 pub debug_terminal: Entity<DebugTerminal>,
55 module_list: Entity<module_list::ModuleList>,
56 _console: Entity<Console>,
57 breakpoint_list: Entity<BreakpointList>,
58 panes: PaneGroup,
59 pane_close_subscriptions: HashMap<EntityId, Subscription>,
60 _schedule_serialize: Option<Task<()>>,
61}
62
63impl Render for RunningState {
64 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
65 let active = self.panes.panes().into_iter().next();
66 let x = if let Some(active) = active {
67 self.panes
68 .render(
69 None,
70 &ActivePaneDecorator::new(active, &self.workspace),
71 window,
72 cx,
73 )
74 .into_any_element()
75 } else {
76 div().into_any_element()
77 };
78 let thread_status = self
79 .thread_id
80 .map(|thread_id| self.session.read(cx).thread_status(thread_id))
81 .unwrap_or(ThreadStatus::Exited);
82
83 self.variable_list.update(cx, |this, cx| {
84 this.disabled(thread_status != ThreadStatus::Stopped, cx);
85 });
86 v_flex()
87 .size_full()
88 .key_context("DebugSessionItem")
89 .track_focus(&self.focus_handle(cx))
90 .child(h_flex().flex_1().child(x))
91 }
92}
93
94pub(crate) struct SubView {
95 inner: AnyView,
96 pane_focus_handle: FocusHandle,
97 kind: DebuggerPaneItem,
98 show_indicator: Box<dyn Fn(&App) -> bool>,
99}
100
101impl SubView {
102 pub(crate) fn new(
103 pane_focus_handle: FocusHandle,
104 view: AnyView,
105 kind: DebuggerPaneItem,
106 show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
107 cx: &mut App,
108 ) -> Entity<Self> {
109 cx.new(|_| Self {
110 kind,
111 inner: view,
112 pane_focus_handle,
113 show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
114 })
115 }
116
117 pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
118 self.kind
119 }
120}
121impl Focusable for SubView {
122 fn focus_handle(&self, _: &App) -> FocusHandle {
123 self.pane_focus_handle.clone()
124 }
125}
126impl EventEmitter<()> for SubView {}
127impl Item for SubView {
128 type Event = ();
129
130 /// This is used to serialize debugger pane layouts
131 /// A SharedString gets converted to a enum and back during serialization/deserialization.
132 fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
133 Some(self.kind.to_shared_string())
134 }
135
136 fn tab_content(
137 &self,
138 params: workspace::item::TabContentParams,
139 _: &Window,
140 cx: &App,
141 ) -> AnyElement {
142 let label = Label::new(self.kind.to_shared_string())
143 .size(ui::LabelSize::Small)
144 .color(params.text_color())
145 .line_height_style(ui::LineHeightStyle::UiLabel);
146
147 if !params.selected && self.show_indicator.as_ref()(cx) {
148 return h_flex()
149 .justify_between()
150 .child(ui::Indicator::dot())
151 .gap_2()
152 .child(label)
153 .into_any_element();
154 }
155
156 label.into_any_element()
157 }
158}
159
160impl Render for SubView {
161 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
162 v_flex().size_full().child(self.inner.clone())
163 }
164}
165
166pub(crate) fn new_debugger_pane(
167 workspace: WeakEntity<Workspace>,
168 project: Entity<Project>,
169 window: &mut Window,
170 cx: &mut Context<RunningState>,
171) -> Entity<Pane> {
172 let weak_running = cx.weak_entity();
173 let custom_drop_handle = {
174 let workspace = workspace.clone();
175 let project = project.downgrade();
176 let weak_running = weak_running.clone();
177 move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {
178 let Some(tab) = any.downcast_ref::<DraggedTab>() else {
179 return ControlFlow::Break(());
180 };
181 let Some(project) = project.upgrade() else {
182 return ControlFlow::Break(());
183 };
184 let this_pane = cx.entity().clone();
185 let item = if tab.pane == this_pane {
186 pane.item_for_index(tab.ix)
187 } else {
188 tab.pane.read(cx).item_for_index(tab.ix)
189 };
190 let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {
191 return ControlFlow::Break(());
192 };
193
194 let source = tab.pane.clone();
195 let item_id_to_move = item.item_id();
196
197 let Ok(new_split_pane) = pane
198 .drag_split_direction()
199 .map(|split_direction| {
200 weak_running.update(cx, |running, cx| {
201 let new_pane =
202 new_debugger_pane(workspace.clone(), project.clone(), window, cx);
203 let _previous_subscription = running.pane_close_subscriptions.insert(
204 new_pane.entity_id(),
205 cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
206 );
207 debug_assert!(_previous_subscription.is_none());
208 running
209 .panes
210 .split(&this_pane, &new_pane, split_direction)?;
211 anyhow::Ok(new_pane)
212 })
213 })
214 .transpose()
215 else {
216 return ControlFlow::Break(());
217 };
218
219 match new_split_pane.transpose() {
220 // Source pane may be the one currently updated, so defer the move.
221 Ok(Some(new_pane)) => cx
222 .spawn_in(window, async move |_, cx| {
223 cx.update(|window, cx| {
224 move_item(
225 &source,
226 &new_pane,
227 item_id_to_move,
228 new_pane.read(cx).active_item_index(),
229 window,
230 cx,
231 );
232 })
233 .ok();
234 })
235 .detach(),
236 // If we drop into existing pane or current pane,
237 // regular pane drop handler will take care of it,
238 // using the right tab index for the operation.
239 Ok(None) => return ControlFlow::Continue(()),
240 err @ Err(_) => {
241 err.log_err();
242 return ControlFlow::Break(());
243 }
244 };
245
246 ControlFlow::Break(())
247 }
248 };
249
250 let ret = cx.new(move |cx| {
251 let mut pane = Pane::new(
252 workspace.clone(),
253 project.clone(),
254 Default::default(),
255 None,
256 NoAction.boxed_clone(),
257 window,
258 cx,
259 );
260 pane.set_can_split(Some(Arc::new(move |pane, dragged_item, _window, cx| {
261 if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
262 let is_current_pane = tab.pane == cx.entity();
263 let Some(can_drag_away) = weak_running
264 .update(cx, |running_state, _| {
265 let current_panes = running_state.panes.panes();
266 !current_panes.contains(&&tab.pane)
267 || current_panes.len() > 1
268 || (!is_current_pane || pane.items_len() > 1)
269 })
270 .ok()
271 else {
272 return false;
273 };
274 if can_drag_away {
275 let item = if is_current_pane {
276 pane.item_for_index(tab.ix)
277 } else {
278 tab.pane.read(cx).item_for_index(tab.ix)
279 };
280 if let Some(item) = item {
281 return item.downcast::<SubView>().is_some();
282 }
283 }
284 }
285 false
286 })));
287 pane.display_nav_history_buttons(None);
288 pane.set_custom_drop_handle(cx, custom_drop_handle);
289 pane.set_should_display_tab_bar(|_, _| true);
290 pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));
291 pane.set_render_tab_bar(cx, |pane, window, cx| {
292 let active_pane_item = pane.active_item();
293 h_flex()
294 .w_full()
295 .px_2()
296 .gap_1()
297 .h(Tab::container_height(cx))
298 .drag_over::<DraggedTab>(|bar, _, _, cx| {
299 bar.bg(cx.theme().colors().drop_target_background)
300 })
301 .on_drop(
302 cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| {
303 this.drag_split_direction = None;
304 this.handle_tab_drop(dragged_tab, this.items_len(), window, cx)
305 }),
306 )
307 .bg(cx.theme().colors().tab_bar_background)
308 .border_b_1()
309 .border_color(cx.theme().colors().border)
310 .children(pane.items().enumerate().map(|(ix, item)| {
311 let selected = active_pane_item
312 .as_ref()
313 .map_or(false, |active| active.item_id() == item.item_id());
314 let item_ = item.boxed_clone();
315 div()
316 .id(SharedString::from(format!(
317 "debugger_tab_{}",
318 item.item_id().as_u64()
319 )))
320 .p_1()
321 .rounded_md()
322 .cursor_pointer()
323 .map(|this| {
324 if selected {
325 this.bg(cx.theme().colors().tab_active_background)
326 } else {
327 let hover_color = cx.theme().colors().element_hover;
328 this.hover(|style| style.bg(hover_color))
329 }
330 })
331 .on_click(cx.listener(move |this, _, window, cx| {
332 let index = this.index_for_item(&*item_);
333 if let Some(index) = index {
334 this.activate_item(index, true, true, window, cx);
335 }
336 }))
337 .child(item.tab_content(
338 TabContentParams {
339 selected,
340 ..Default::default()
341 },
342 window,
343 cx,
344 ))
345 .on_drop(
346 cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| {
347 this.drag_split_direction = None;
348 this.handle_tab_drop(dragged_tab, ix, window, cx)
349 }),
350 )
351 .on_drag(
352 DraggedTab {
353 item: item.boxed_clone(),
354 pane: cx.entity().clone(),
355 detail: 0,
356 is_active: selected,
357 ix,
358 },
359 |tab, _, _, cx| cx.new(|_| tab.clone()),
360 )
361 }))
362 .into_any_element()
363 });
364 pane
365 });
366
367 ret
368}
369
370pub struct DebugTerminal {
371 pub terminal: Option<Entity<TerminalView>>,
372 focus_handle: FocusHandle,
373}
374
375impl DebugTerminal {
376 fn empty(cx: &mut Context<Self>) -> Self {
377 Self {
378 terminal: None,
379 focus_handle: cx.focus_handle(),
380 }
381 }
382}
383
384impl gpui::Render for DebugTerminal {
385 fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
386 if let Some(terminal) = self.terminal.clone() {
387 terminal.into_any_element()
388 } else {
389 div().track_focus(&self.focus_handle).into_any_element()
390 }
391 }
392}
393impl Focusable for DebugTerminal {
394 fn focus_handle(&self, cx: &App) -> FocusHandle {
395 if let Some(terminal) = self.terminal.as_ref() {
396 return terminal.focus_handle(cx);
397 } else {
398 self.focus_handle.clone()
399 }
400 }
401}
402
403impl RunningState {
404 pub fn new(
405 session: Entity<Session>,
406 project: Entity<Project>,
407 workspace: WeakEntity<Workspace>,
408 serialized_pane_layout: Option<SerializedPaneLayout>,
409 window: &mut Window,
410 cx: &mut Context<Self>,
411 ) -> Self {
412 let focus_handle = cx.focus_handle();
413 let session_id = session.read(cx).session_id();
414 let weak_state = cx.weak_entity();
415 let stack_frame_list = cx.new(|cx| {
416 StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx)
417 });
418
419 let debug_terminal = cx.new(DebugTerminal::empty);
420
421 let variable_list =
422 cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx));
423
424 let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
425
426 let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
427
428 let console = cx.new(|cx| {
429 Console::new(
430 session.clone(),
431 stack_frame_list.clone(),
432 variable_list.clone(),
433 window,
434 cx,
435 )
436 });
437
438 let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
439
440 let _subscriptions = vec![
441 cx.observe(&module_list, |_, _, cx| cx.notify()),
442 cx.subscribe_in(&session, window, |this, _, event, window, cx| {
443 match event {
444 SessionEvent::Stopped(thread_id) => {
445 this.workspace
446 .update(cx, |workspace, cx| {
447 workspace.open_panel::<crate::DebugPanel>(window, cx);
448 })
449 .log_err();
450
451 if let Some(thread_id) = thread_id {
452 this.select_thread(*thread_id, window, cx);
453 }
454 }
455 SessionEvent::Threads => {
456 let threads = this.session.update(cx, |this, cx| this.threads(cx));
457 this.select_current_thread(&threads, window, cx);
458 }
459 SessionEvent::CapabilitiesLoaded => {
460 let capabilities = this.capabilities(cx);
461 if !capabilities.supports_modules_request.unwrap_or(false) {
462 this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
463 }
464 if !capabilities
465 .supports_loaded_sources_request
466 .unwrap_or(false)
467 {
468 this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
469 }
470 }
471
472 _ => {}
473 }
474 cx.notify()
475 }),
476 cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
477 this.serialize_layout(window, cx);
478 }),
479 ];
480
481 let mut pane_close_subscriptions = HashMap::default();
482 let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
483 persistence::deserialize_pane_layout(
484 serialized_layout,
485 &workspace,
486 &project,
487 &stack_frame_list,
488 &variable_list,
489 &module_list,
490 &console,
491 &breakpoint_list,
492 &loaded_source_list,
493 &debug_terminal,
494 &mut pane_close_subscriptions,
495 window,
496 cx,
497 )
498 }) {
499 workspace::PaneGroup::with_root(root)
500 } else {
501 pane_close_subscriptions.clear();
502
503 let root = Self::default_pane_layout(
504 project,
505 &workspace,
506 &stack_frame_list,
507 &variable_list,
508 &module_list,
509 &loaded_source_list,
510 &console,
511 &breakpoint_list,
512 &mut pane_close_subscriptions,
513 window,
514 cx,
515 );
516
517 workspace::PaneGroup::with_root(root)
518 };
519
520 Self {
521 session,
522 workspace,
523 focus_handle,
524 variable_list,
525 _subscriptions,
526 thread_id: None,
527 _remote_id: None,
528 stack_frame_list,
529 session_id,
530 panes,
531 module_list,
532 _console: console,
533 breakpoint_list,
534 loaded_sources_list: loaded_source_list,
535 pane_close_subscriptions,
536 debug_terminal,
537 _schedule_serialize: None,
538 }
539 }
540
541 pub(crate) fn remove_pane_item(
542 &mut self,
543 item_kind: DebuggerPaneItem,
544 window: &mut Window,
545 cx: &mut Context<Self>,
546 ) {
547 if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
548 Some(pane).zip(
549 pane.read(cx)
550 .items()
551 .find(|item| {
552 item.act_as::<SubView>(cx)
553 .is_some_and(|view| view.read(cx).kind == item_kind)
554 })
555 .map(|item| item.item_id()),
556 )
557 }) {
558 pane.update(cx, |pane, cx| {
559 pane.remove_item(item_id, false, true, window, cx)
560 })
561 }
562 }
563
564 pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
565 self.panes.pane_at_pixel_position(position).is_some()
566 }
567
568 fn create_sub_view(
569 &self,
570 item_kind: DebuggerPaneItem,
571 pane: &Entity<Pane>,
572 cx: &mut Context<Self>,
573 ) -> Box<dyn ItemHandle> {
574 match item_kind {
575 DebuggerPaneItem::Console => {
576 let weak_console = self._console.clone().downgrade();
577
578 Box::new(SubView::new(
579 pane.focus_handle(cx),
580 self._console.clone().into(),
581 item_kind,
582 Some(Box::new(move |cx| {
583 weak_console
584 .read_with(cx, |console, cx| console.show_indicator(cx))
585 .unwrap_or_default()
586 })),
587 cx,
588 ))
589 }
590 DebuggerPaneItem::Variables => Box::new(SubView::new(
591 self.variable_list.focus_handle(cx),
592 self.variable_list.clone().into(),
593 item_kind,
594 None,
595 cx,
596 )),
597 DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
598 self.breakpoint_list.focus_handle(cx),
599 self.breakpoint_list.clone().into(),
600 item_kind,
601 None,
602 cx,
603 )),
604 DebuggerPaneItem::Frames => Box::new(SubView::new(
605 self.stack_frame_list.focus_handle(cx),
606 self.stack_frame_list.clone().into(),
607 item_kind,
608 None,
609 cx,
610 )),
611 DebuggerPaneItem::Modules => Box::new(SubView::new(
612 self.module_list.focus_handle(cx),
613 self.module_list.clone().into(),
614 item_kind,
615 None,
616 cx,
617 )),
618 DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
619 self.loaded_sources_list.focus_handle(cx),
620 self.loaded_sources_list.clone().into(),
621 item_kind,
622 None,
623 cx,
624 )),
625 DebuggerPaneItem::Terminal => Box::new(SubView::new(
626 self.debug_terminal.focus_handle(cx),
627 self.debug_terminal.clone().into(),
628 item_kind,
629 None,
630 cx,
631 )),
632 }
633 }
634
635 pub(crate) fn ensure_pane_item(
636 &mut self,
637 item_kind: DebuggerPaneItem,
638 window: &mut Window,
639 cx: &mut Context<Self>,
640 ) {
641 if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
642 return;
643 };
644 let pane = self.panes.last_pane();
645 let sub_view = self.create_sub_view(item_kind, &pane, cx);
646
647 pane.update(cx, |pane, cx| {
648 pane.add_item_inner(sub_view, false, false, false, None, window, cx);
649 })
650 }
651
652 pub(crate) fn add_pane_item(
653 &mut self,
654 item_kind: DebuggerPaneItem,
655 position: Point<Pixels>,
656 window: &mut Window,
657 cx: &mut Context<Self>,
658 ) {
659 debug_assert!(
660 item_kind.is_supported(self.session.read(cx).capabilities()),
661 "We should only allow adding supported item kinds"
662 );
663
664 if let Some(pane) = self.panes.pane_at_pixel_position(position) {
665 let sub_view = self.create_sub_view(item_kind, pane, cx);
666
667 pane.update(cx, |pane, cx| {
668 pane.add_item(sub_view, false, false, None, window, cx);
669 })
670 }
671 }
672
673 pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
674 let caps = self.session.read(cx).capabilities();
675 let mut pane_item_status = IndexMap::from_iter(
676 DebuggerPaneItem::all()
677 .iter()
678 .filter(|kind| kind.is_supported(&caps))
679 .map(|kind| (*kind, false)),
680 );
681 self.panes.panes().iter().for_each(|pane| {
682 pane.read(cx)
683 .items()
684 .filter_map(|item| item.act_as::<SubView>(cx))
685 .for_each(|view| {
686 pane_item_status.insert(view.read(cx).kind, true);
687 });
688 });
689
690 pane_item_status
691 }
692
693 pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
694 if self._schedule_serialize.is_none() {
695 self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
696 cx.background_executor()
697 .timer(Duration::from_millis(100))
698 .await;
699
700 let Some((adapter_name, pane_group)) = this
701 .update(cx, |this, cx| {
702 let adapter_name = this.session.read(cx).adapter_name();
703 (
704 adapter_name,
705 persistence::build_serialized_pane_layout(&this.panes.root, cx),
706 )
707 })
708 .ok()
709 else {
710 return;
711 };
712
713 persistence::serialize_pane_layout(adapter_name, pane_group)
714 .await
715 .log_err();
716
717 this.update(cx, |this, _| {
718 this._schedule_serialize.take();
719 })
720 .ok();
721 }));
722 }
723 }
724
725 pub(crate) fn handle_pane_event(
726 this: &mut RunningState,
727 source_pane: &Entity<Pane>,
728 event: &Event,
729 window: &mut Window,
730 cx: &mut Context<RunningState>,
731 ) {
732 this.serialize_layout(window, cx);
733 if let Event::Remove { .. } = event {
734 let _did_find_pane = this.panes.remove(&source_pane).is_ok();
735 debug_assert!(_did_find_pane);
736 cx.notify();
737 }
738 }
739
740 pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
741 if self.thread_id.is_some() {
742 self.stack_frame_list
743 .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx));
744 }
745 }
746
747 pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
748 self.variable_list.read(cx).has_open_context_menu()
749 }
750
751 pub fn session(&self) -> &Entity<Session> {
752 &self.session
753 }
754
755 pub fn session_id(&self) -> SessionId {
756 self.session_id
757 }
758
759 pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
760 self.stack_frame_list.read(cx).selected_stack_frame_id()
761 }
762
763 #[cfg(test)]
764 pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
765 &self.stack_frame_list
766 }
767
768 #[cfg(test)]
769 pub fn console(&self) -> &Entity<Console> {
770 &self._console
771 }
772
773 #[cfg(test)]
774 pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
775 &self.module_list
776 }
777
778 #[cfg(test)]
779 pub(crate) fn activate_modules_list(&self, window: &mut Window, cx: &mut App) {
780 let (variable_list_position, pane) = self
781 .panes
782 .panes()
783 .into_iter()
784 .find_map(|pane| {
785 pane.read(cx)
786 .items_of_type::<SubView>()
787 .position(|view| view.read(cx).view_kind().to_shared_string() == *"Modules")
788 .map(|view| (view, pane))
789 })
790 .unwrap();
791 pane.update(cx, |this, cx| {
792 this.activate_item(variable_list_position, true, true, window, cx);
793 })
794 }
795 #[cfg(test)]
796 pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
797 &self.variable_list
798 }
799
800 pub fn capabilities(&self, cx: &App) -> Capabilities {
801 self.session().read(cx).capabilities().clone()
802 }
803
804 pub fn select_current_thread(
805 &mut self,
806 threads: &Vec<(Thread, ThreadStatus)>,
807 window: &mut Window,
808 cx: &mut Context<Self>,
809 ) {
810 let selected_thread = self
811 .thread_id
812 .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
813 .or_else(|| threads.first());
814
815 let Some((selected_thread, _)) = selected_thread else {
816 return;
817 };
818
819 if Some(ThreadId(selected_thread.id)) != self.thread_id {
820 self.select_thread(ThreadId(selected_thread.id), window, cx);
821 }
822 }
823
824 pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
825 self.thread_id
826 }
827
828 pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
829 self.thread_id
830 .map(|id| self.session().read(cx).thread_status(id))
831 }
832
833 fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
834 if self.thread_id.is_some_and(|id| id == thread_id) {
835 return;
836 }
837
838 self.thread_id = Some(thread_id);
839
840 self.stack_frame_list
841 .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
842 }
843
844 pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
845 let Some(thread_id) = self.thread_id else {
846 return;
847 };
848
849 self.session().update(cx, |state, cx| {
850 state.continue_thread(thread_id, cx);
851 });
852 }
853
854 pub fn step_over(&mut self, cx: &mut Context<Self>) {
855 let Some(thread_id) = self.thread_id else {
856 return;
857 };
858
859 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
860
861 self.session().update(cx, |state, cx| {
862 state.step_over(thread_id, granularity, cx);
863 });
864 }
865
866 pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
867 let Some(thread_id) = self.thread_id else {
868 return;
869 };
870
871 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
872
873 self.session().update(cx, |state, cx| {
874 state.step_in(thread_id, granularity, cx);
875 });
876 }
877
878 pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
879 let Some(thread_id) = self.thread_id else {
880 return;
881 };
882
883 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
884
885 self.session().update(cx, |state, cx| {
886 state.step_out(thread_id, granularity, cx);
887 });
888 }
889
890 pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
891 let Some(thread_id) = self.thread_id else {
892 return;
893 };
894
895 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
896
897 self.session().update(cx, |state, cx| {
898 state.step_back(thread_id, granularity, cx);
899 });
900 }
901
902 pub fn restart_session(&self, cx: &mut Context<Self>) {
903 self.session().update(cx, |state, cx| {
904 state.restart(None, cx);
905 });
906 }
907
908 pub fn pause_thread(&self, cx: &mut Context<Self>) {
909 let Some(thread_id) = self.thread_id else {
910 return;
911 };
912
913 self.session().update(cx, |state, cx| {
914 state.pause_thread(thread_id, cx);
915 });
916 }
917
918 pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
919 self.workspace
920 .update(cx, |workspace, cx| {
921 workspace
922 .project()
923 .read(cx)
924 .breakpoint_store()
925 .update(cx, |store, cx| {
926 store.remove_active_position(Some(self.session_id), cx)
927 })
928 })
929 .log_err();
930
931 self.session.update(cx, |session, cx| {
932 session.shutdown(cx).detach();
933 })
934 }
935
936 pub fn stop_thread(&self, cx: &mut Context<Self>) {
937 let Some(thread_id) = self.thread_id else {
938 return;
939 };
940
941 self.workspace
942 .update(cx, |workspace, cx| {
943 workspace
944 .project()
945 .read(cx)
946 .breakpoint_store()
947 .update(cx, |store, cx| {
948 store.remove_active_position(Some(self.session_id), cx)
949 })
950 })
951 .log_err();
952
953 self.session().update(cx, |state, cx| {
954 state.terminate_threads(Some(vec![thread_id; 1]), cx);
955 });
956 }
957
958 #[expect(
959 unused,
960 reason = "Support for disconnecting a client is not wired through yet"
961 )]
962 pub fn disconnect_client(&self, cx: &mut Context<Self>) {
963 self.session().update(cx, |state, cx| {
964 state.disconnect_client(cx);
965 });
966 }
967
968 pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
969 self.session.update(cx, |session, cx| {
970 session.toggle_ignore_breakpoints(cx).detach();
971 });
972 }
973
974 pub(crate) fn thread_dropdown(
975 &self,
976 window: &mut Window,
977 cx: &mut Context<'_, RunningState>,
978 ) -> DropdownMenu {
979 let state = cx.entity();
980 let threads = self.session.update(cx, |this, cx| this.threads(cx));
981 let selected_thread_name = threads
982 .iter()
983 .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
984 .map(|(thread, _)| thread.name.clone())
985 .unwrap_or("Threads".to_owned());
986 DropdownMenu::new(
987 ("thread-list", self.session_id.0),
988 selected_thread_name,
989 ContextMenu::build_eager(window, cx, move |mut this, _, _| {
990 for (thread, _) in threads {
991 let state = state.clone();
992 let thread_id = thread.id;
993 this = this.entry(thread.name, None, move |window, cx| {
994 state.update(cx, |state, cx| {
995 state.select_thread(ThreadId(thread_id), window, cx);
996 });
997 });
998 }
999 this
1000 }),
1001 )
1002 }
1003
1004 fn default_pane_layout(
1005 project: Entity<Project>,
1006 workspace: &WeakEntity<Workspace>,
1007 stack_frame_list: &Entity<StackFrameList>,
1008 variable_list: &Entity<VariableList>,
1009 module_list: &Entity<ModuleList>,
1010 loaded_source_list: &Entity<LoadedSourceList>,
1011 console: &Entity<Console>,
1012 breakpoints: &Entity<BreakpointList>,
1013 subscriptions: &mut HashMap<EntityId, Subscription>,
1014 window: &mut Window,
1015 cx: &mut Context<'_, RunningState>,
1016 ) -> Member {
1017 let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1018 leftmost_pane.update(cx, |this, cx| {
1019 this.add_item(
1020 Box::new(SubView::new(
1021 this.focus_handle(cx),
1022 stack_frame_list.clone().into(),
1023 DebuggerPaneItem::Frames,
1024 None,
1025 cx,
1026 )),
1027 true,
1028 false,
1029 None,
1030 window,
1031 cx,
1032 );
1033 this.add_item(
1034 Box::new(SubView::new(
1035 breakpoints.focus_handle(cx),
1036 breakpoints.clone().into(),
1037 DebuggerPaneItem::BreakpointList,
1038 None,
1039 cx,
1040 )),
1041 true,
1042 false,
1043 None,
1044 window,
1045 cx,
1046 );
1047 this.activate_item(0, false, false, window, cx);
1048 });
1049 let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1050
1051 center_pane.update(cx, |this, cx| {
1052 this.add_item(
1053 Box::new(SubView::new(
1054 variable_list.focus_handle(cx),
1055 variable_list.clone().into(),
1056 DebuggerPaneItem::Variables,
1057 None,
1058 cx,
1059 )),
1060 true,
1061 false,
1062 None,
1063 window,
1064 cx,
1065 );
1066 this.add_item(
1067 Box::new(SubView::new(
1068 module_list.focus_handle(cx),
1069 module_list.clone().into(),
1070 DebuggerPaneItem::Modules,
1071 None,
1072 cx,
1073 )),
1074 false,
1075 false,
1076 None,
1077 window,
1078 cx,
1079 );
1080
1081 this.add_item(
1082 Box::new(SubView::new(
1083 loaded_source_list.focus_handle(cx),
1084 loaded_source_list.clone().into(),
1085 DebuggerPaneItem::LoadedSources,
1086 None,
1087 cx,
1088 )),
1089 false,
1090 false,
1091 None,
1092 window,
1093 cx,
1094 );
1095 this.activate_item(0, false, false, window, cx);
1096 });
1097
1098 let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1099 rightmost_pane.update(cx, |this, cx| {
1100 let weak_console = console.downgrade();
1101 this.add_item(
1102 Box::new(SubView::new(
1103 this.focus_handle(cx),
1104 console.clone().into(),
1105 DebuggerPaneItem::Console,
1106 Some(Box::new(move |cx| {
1107 weak_console
1108 .read_with(cx, |console, cx| console.show_indicator(cx))
1109 .unwrap_or_default()
1110 })),
1111 cx,
1112 )),
1113 true,
1114 false,
1115 None,
1116 window,
1117 cx,
1118 );
1119 });
1120
1121 subscriptions.extend(
1122 [&leftmost_pane, ¢er_pane, &rightmost_pane]
1123 .into_iter()
1124 .map(|entity| {
1125 (
1126 entity.entity_id(),
1127 cx.subscribe_in(entity, window, Self::handle_pane_event),
1128 )
1129 }),
1130 );
1131
1132 let group_root = workspace::PaneAxis::new(
1133 gpui::Axis::Horizontal,
1134 [leftmost_pane, center_pane, rightmost_pane]
1135 .into_iter()
1136 .map(workspace::Member::Pane)
1137 .collect(),
1138 );
1139
1140 Member::Axis(group_root)
1141 }
1142}
1143
1144impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1145
1146impl Focusable for RunningState {
1147 fn focus_handle(&self, _: &App) -> FocusHandle {
1148 self.focus_handle.clone()
1149 }
1150}