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