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