1pub mod running;
2
3use std::sync::OnceLock;
4
5use dap::client::SessionId;
6use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
7use project::Project;
8use project::debugger::session::Session;
9use project::worktree_store::WorktreeStore;
10use rpc::proto::{self, PeerId};
11use running::RunningState;
12use ui::{Indicator, prelude::*};
13use workspace::{
14 FollowableItem, ViewId, Workspace,
15 item::{self, Item},
16};
17
18use crate::debugger_panel::DebugPanel;
19use crate::persistence::SerializedPaneLayout;
20
21pub struct DebugSession {
22 remote_id: Option<workspace::ViewId>,
23 running_state: Entity<RunningState>,
24 label: OnceLock<SharedString>,
25 _debug_panel: WeakEntity<DebugPanel>,
26 _worktree_store: WeakEntity<WorktreeStore>,
27 _workspace: WeakEntity<Workspace>,
28 _subscriptions: [Subscription; 1],
29}
30
31#[derive(Debug)]
32pub enum DebugPanelItemEvent {
33 Close,
34 Stopped { go_to_stack_frame: bool },
35}
36
37impl DebugSession {
38 pub(crate) fn running(
39 project: Entity<Project>,
40 workspace: WeakEntity<Workspace>,
41 session: Entity<Session>,
42 _debug_panel: WeakEntity<DebugPanel>,
43 serialized_pane_layout: Option<SerializedPaneLayout>,
44 window: &mut Window,
45 cx: &mut App,
46 ) -> Entity<Self> {
47 let running_state = cx.new(|cx| {
48 RunningState::new(
49 session.clone(),
50 project.clone(),
51 workspace.clone(),
52 serialized_pane_layout,
53 window,
54 cx,
55 )
56 });
57
58 cx.new(|cx| Self {
59 _subscriptions: [cx.subscribe(&running_state, |_, _, _, cx| {
60 cx.notify();
61 })],
62 remote_id: None,
63 running_state,
64 label: OnceLock::new(),
65 _debug_panel,
66 _worktree_store: project.read(cx).worktree_store().downgrade(),
67 _workspace: workspace,
68 })
69 }
70
71 pub(crate) fn session_id(&self, cx: &App) -> SessionId {
72 self.running_state.read(cx).session_id()
73 }
74
75 pub fn session(&self, cx: &App) -> Entity<Session> {
76 self.running_state.read(cx).session().clone()
77 }
78
79 pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
80 self.running_state
81 .update(cx, |state, cx| state.shutdown(cx));
82 }
83
84 pub(crate) fn label(&self, cx: &App) -> SharedString {
85 if let Some(label) = self.label.get() {
86 return label.clone();
87 }
88
89 let session = self.running_state.read(cx).session();
90
91 self.label
92 .get_or_init(|| session.read(cx).label())
93 .to_owned()
94 }
95
96 pub(crate) fn running_state(&self) -> &Entity<RunningState> {
97 &self.running_state
98 }
99
100 pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
101 let label = self.label(cx);
102
103 let icon = {
104 if self
105 .running_state
106 .read(cx)
107 .session()
108 .read(cx)
109 .is_terminated()
110 {
111 Some(Indicator::dot().color(Color::Error))
112 } else {
113 match self
114 .running_state
115 .read(cx)
116 .thread_status(cx)
117 .unwrap_or_default()
118 {
119 project::debugger::session::ThreadStatus::Stopped => {
120 Some(Indicator::dot().color(Color::Conflict))
121 }
122 _ => Some(Indicator::dot().color(Color::Success)),
123 }
124 }
125 };
126
127 h_flex()
128 .gap_2()
129 .when_some(icon, |this, indicator| this.child(indicator))
130 .justify_between()
131 .child(Label::new(label))
132 .into_any_element()
133 }
134}
135
136impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
137
138impl Focusable for DebugSession {
139 fn focus_handle(&self, cx: &App) -> FocusHandle {
140 self.running_state.focus_handle(cx)
141 }
142}
143
144impl Item for DebugSession {
145 type Event = DebugPanelItemEvent;
146 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
147 "Debugger".into()
148 }
149}
150
151impl FollowableItem for DebugSession {
152 fn remote_id(&self) -> Option<workspace::ViewId> {
153 self.remote_id
154 }
155
156 fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option<proto::view::Variant> {
157 None
158 }
159
160 fn from_state_proto(
161 _workspace: Entity<Workspace>,
162 _remote_id: ViewId,
163 _state: &mut Option<proto::view::Variant>,
164 _window: &mut Window,
165 _cx: &mut App,
166 ) -> Option<gpui::Task<gpui::Result<Entity<Self>>>> {
167 None
168 }
169
170 fn add_event_to_update_proto(
171 &self,
172 _event: &Self::Event,
173 _update: &mut Option<proto::update_view::Variant>,
174 _window: &Window,
175 _cx: &App,
176 ) -> bool {
177 // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default()));
178
179 true
180 }
181
182 fn apply_update_proto(
183 &mut self,
184 _project: &Entity<project::Project>,
185 _message: proto::update_view::Variant,
186 _window: &mut Window,
187 _cx: &mut Context<Self>,
188 ) -> gpui::Task<gpui::Result<()>> {
189 Task::ready(Ok(()))
190 }
191
192 fn set_leader_peer_id(
193 &mut self,
194 _leader_peer_id: Option<PeerId>,
195 _window: &mut Window,
196 _cx: &mut Context<Self>,
197 ) {
198 }
199
200 fn to_follow_event(_event: &Self::Event) -> Option<workspace::item::FollowEvent> {
201 None
202 }
203
204 fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<workspace::item::Dedup> {
205 if existing.session_id(cx) == self.session_id(cx) {
206 Some(item::Dedup::KeepExisting)
207 } else {
208 None
209 }
210 }
211
212 fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
213 true
214 }
215}
216
217impl Render for DebugSession {
218 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
219 self.running_state
220 .update(cx, |this, cx| this.render(window, cx).into_any_element())
221 }
222}