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