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 label(&self, cx: &App) -> SharedString {
108 if let Some(label) = self.label.get() {
109 return label.clone();
110 }
111
112 let session_id = match &self.mode {
113 DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
114 };
115
116 let Ok(Some(session)) = self
117 .dap_store
118 .read_with(cx, |store, _| store.session_by_id(session_id))
119 else {
120 return "".into();
121 };
122
123 self.label
124 .get_or_init(|| session.read(cx).label())
125 .to_owned()
126 }
127
128 #[allow(unused)]
129 pub(crate) fn running_state(&self) -> Entity<RunningState> {
130 match &self.mode {
131 DebugSessionState::Running(running_state) => running_state.clone(),
132 }
133 }
134
135 pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
136 let label = self.label(cx);
137
138 let icon = match &self.mode {
139 DebugSessionState::Running(state) => {
140 if state.read(cx).session().read(cx).is_terminated() {
141 Some(Indicator::dot().color(Color::Error))
142 } else {
143 match state.read(cx).thread_status(cx).unwrap_or_default() {
144 project::debugger::session::ThreadStatus::Stopped => {
145 Some(Indicator::dot().color(Color::Conflict))
146 }
147 _ => Some(Indicator::dot().color(Color::Success)),
148 }
149 }
150 }
151 };
152
153 h_flex()
154 .gap_2()
155 .when_some(icon, |this, indicator| this.child(indicator))
156 .justify_between()
157 .child(Label::new(label))
158 .into_any_element()
159 }
160}
161
162impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
163
164impl Focusable for DebugSession {
165 fn focus_handle(&self, cx: &App) -> FocusHandle {
166 match &self.mode {
167 DebugSessionState::Running(running_state) => running_state.focus_handle(cx),
168 }
169 }
170}
171
172impl Item for DebugSession {
173 type Event = DebugPanelItemEvent;
174 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
175 "Debugger".into()
176 }
177}
178
179impl FollowableItem for DebugSession {
180 fn remote_id(&self) -> Option<workspace::ViewId> {
181 self.remote_id
182 }
183
184 fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option<proto::view::Variant> {
185 None
186 }
187
188 fn from_state_proto(
189 _workspace: Entity<Workspace>,
190 _remote_id: ViewId,
191 _state: &mut Option<proto::view::Variant>,
192 _window: &mut Window,
193 _cx: &mut App,
194 ) -> Option<gpui::Task<gpui::Result<Entity<Self>>>> {
195 None
196 }
197
198 fn add_event_to_update_proto(
199 &self,
200 _event: &Self::Event,
201 _update: &mut Option<proto::update_view::Variant>,
202 _window: &Window,
203 _cx: &App,
204 ) -> bool {
205 // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default()));
206
207 true
208 }
209
210 fn apply_update_proto(
211 &mut self,
212 _project: &Entity<project::Project>,
213 _message: proto::update_view::Variant,
214 _window: &mut Window,
215 _cx: &mut Context<Self>,
216 ) -> gpui::Task<gpui::Result<()>> {
217 Task::ready(Ok(()))
218 }
219
220 fn set_leader_peer_id(
221 &mut self,
222 _leader_peer_id: Option<PeerId>,
223 _window: &mut Window,
224 _cx: &mut Context<Self>,
225 ) {
226 }
227
228 fn to_follow_event(_event: &Self::Event) -> Option<workspace::item::FollowEvent> {
229 None
230 }
231
232 fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<workspace::item::Dedup> {
233 if existing.session_id(cx) == self.session_id(cx) {
234 Some(item::Dedup::KeepExisting)
235 } else {
236 None
237 }
238 }
239
240 fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
241 true
242 }
243}
244
245impl Render for DebugSession {
246 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
247 match &self.mode {
248 DebugSessionState::Running(running_state) => {
249 running_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
250 }
251 }
252 }
253}