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