1use collections::HashMap;
2use dap::Capabilities;
3use db::kvp::KEY_VALUE_STORE;
4use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window};
5use project::Project;
6use serde::{Deserialize, Serialize};
7use ui::{App, SharedString};
8use util::ResultExt;
9use workspace::{Member, Pane, PaneAxis, Workspace};
10
11use crate::session::running::{
12 self, RunningState, SubView, breakpoint_list::BreakpointList, console::Console,
13 loaded_source_list::LoadedSourceList, module_list::ModuleList,
14 stack_frame_list::StackFrameList, variable_list::VariableList,
15};
16
17#[derive(Clone, Hash, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
18pub(crate) enum DebuggerPaneItem {
19 Console,
20 Variables,
21 BreakpointList,
22 Frames,
23 Modules,
24 LoadedSources,
25}
26
27impl DebuggerPaneItem {
28 pub(crate) fn all() -> &'static [DebuggerPaneItem] {
29 static VARIANTS: &[DebuggerPaneItem] = &[
30 DebuggerPaneItem::Console,
31 DebuggerPaneItem::Variables,
32 DebuggerPaneItem::BreakpointList,
33 DebuggerPaneItem::Frames,
34 DebuggerPaneItem::Modules,
35 DebuggerPaneItem::LoadedSources,
36 ];
37 VARIANTS
38 }
39
40 pub(crate) fn is_supported(&self, capabilities: &Capabilities) -> bool {
41 match self {
42 DebuggerPaneItem::Modules => capabilities.supports_modules_request.unwrap_or_default(),
43 DebuggerPaneItem::LoadedSources => capabilities
44 .supports_loaded_sources_request
45 .unwrap_or_default(),
46 _ => true,
47 }
48 }
49
50 pub(crate) fn to_shared_string(self) -> SharedString {
51 match self {
52 DebuggerPaneItem::Console => SharedString::new_static("Console"),
53 DebuggerPaneItem::Variables => SharedString::new_static("Variables"),
54 DebuggerPaneItem::BreakpointList => SharedString::new_static("Breakpoints"),
55 DebuggerPaneItem::Frames => SharedString::new_static("Frames"),
56 DebuggerPaneItem::Modules => SharedString::new_static("Modules"),
57 DebuggerPaneItem::LoadedSources => SharedString::new_static("Sources"),
58 }
59 }
60}
61
62impl From<DebuggerPaneItem> for SharedString {
63 fn from(item: DebuggerPaneItem) -> Self {
64 item.to_shared_string()
65 }
66}
67
68#[derive(Debug, Serialize, Deserialize)]
69pub(crate) struct SerializedAxis(pub Axis);
70
71#[derive(Debug, Serialize, Deserialize)]
72pub(crate) enum SerializedPaneLayout {
73 Pane(SerializedPane),
74 Group {
75 axis: SerializedAxis,
76 flexes: Option<Vec<f32>>,
77 children: Vec<SerializedPaneLayout>,
78 },
79}
80
81#[derive(Debug, Serialize, Deserialize)]
82pub(crate) struct SerializedPane {
83 pub children: Vec<DebuggerPaneItem>,
84 pub active_item: Option<DebuggerPaneItem>,
85}
86
87const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
88
89pub(crate) async fn serialize_pane_layout(
90 adapter_name: SharedString,
91 pane_group: SerializedPaneLayout,
92) -> anyhow::Result<()> {
93 if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) {
94 KEY_VALUE_STORE
95 .write_kvp(
96 format!("{DEBUGGER_PANEL_PREFIX}-{adapter_name}"),
97 serialized_pane_group,
98 )
99 .await
100 } else {
101 Err(anyhow::anyhow!(
102 "Failed to serialize pane group with serde_json as a string"
103 ))
104 }
105}
106
107pub(crate) fn build_serialized_pane_layout(
108 pane_group: &Member,
109 cx: &mut App,
110) -> SerializedPaneLayout {
111 match pane_group {
112 Member::Axis(PaneAxis {
113 axis,
114 members,
115 flexes,
116 bounding_boxes: _,
117 }) => SerializedPaneLayout::Group {
118 axis: SerializedAxis(*axis),
119 children: members
120 .iter()
121 .map(|member| build_serialized_pane_layout(member, cx))
122 .collect::<Vec<_>>(),
123 flexes: Some(flexes.lock().clone()),
124 },
125 Member::Pane(pane_handle) => SerializedPaneLayout::Pane(serialize_pane(pane_handle, cx)),
126 }
127}
128
129fn serialize_pane(pane: &Entity<Pane>, cx: &mut App) -> SerializedPane {
130 let pane = pane.read(cx);
131 let children = pane
132 .items()
133 .filter_map(|item| {
134 item.act_as::<SubView>(cx)
135 .map(|view| view.read(cx).view_kind())
136 })
137 .collect::<Vec<_>>();
138
139 let active_item = pane
140 .active_item()
141 .and_then(|item| item.act_as::<SubView>(cx))
142 .map(|view| view.read(cx).view_kind());
143
144 SerializedPane {
145 children,
146 active_item,
147 }
148}
149
150pub(crate) async fn get_serialized_pane_layout(
151 adapter_name: impl AsRef<str>,
152) -> Option<SerializedPaneLayout> {
153 let key = format!("{DEBUGGER_PANEL_PREFIX}-{}", adapter_name.as_ref());
154
155 KEY_VALUE_STORE
156 .read_kvp(&key)
157 .log_err()
158 .flatten()
159 .and_then(|value| serde_json::from_str::<SerializedPaneLayout>(&value).ok())
160}
161
162pub(crate) fn deserialize_pane_layout(
163 serialized: SerializedPaneLayout,
164 workspace: &WeakEntity<Workspace>,
165 project: &Entity<Project>,
166 stack_frame_list: &Entity<StackFrameList>,
167 variable_list: &Entity<VariableList>,
168 module_list: &Entity<ModuleList>,
169 console: &Entity<Console>,
170 breakpoint_list: &Entity<BreakpointList>,
171 loaded_sources: &Entity<LoadedSourceList>,
172 subscriptions: &mut HashMap<EntityId, Subscription>,
173 window: &mut Window,
174 cx: &mut Context<RunningState>,
175) -> Option<Member> {
176 match serialized {
177 SerializedPaneLayout::Group {
178 axis,
179 flexes,
180 children,
181 } => {
182 let mut members = Vec::new();
183 for child in children {
184 if let Some(new_member) = deserialize_pane_layout(
185 child,
186 workspace,
187 project,
188 stack_frame_list,
189 variable_list,
190 module_list,
191 console,
192 breakpoint_list,
193 loaded_sources,
194 subscriptions,
195 window,
196 cx,
197 ) {
198 members.push(new_member);
199 }
200 }
201
202 if members.is_empty() {
203 return None;
204 }
205
206 if members.len() == 1 {
207 return Some(members.remove(0));
208 }
209
210 Some(Member::Axis(PaneAxis::load(
211 axis.0,
212 members,
213 flexes.clone(),
214 )))
215 }
216 SerializedPaneLayout::Pane(serialized_pane) => {
217 let pane = running::new_debugger_pane(workspace.clone(), project.clone(), window, cx);
218 subscriptions.insert(
219 pane.entity_id(),
220 cx.subscribe_in(&pane, window, RunningState::handle_pane_event),
221 );
222
223 let sub_views: Vec<_> = serialized_pane
224 .children
225 .iter()
226 .map(|child| match child {
227 DebuggerPaneItem::Frames => Box::new(SubView::new(
228 stack_frame_list.focus_handle(cx),
229 stack_frame_list.clone().into(),
230 DebuggerPaneItem::Frames,
231 None,
232 cx,
233 )),
234 DebuggerPaneItem::Variables => Box::new(SubView::new(
235 variable_list.focus_handle(cx),
236 variable_list.clone().into(),
237 DebuggerPaneItem::Variables,
238 None,
239 cx,
240 )),
241 DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
242 breakpoint_list.focus_handle(cx),
243 breakpoint_list.clone().into(),
244 DebuggerPaneItem::BreakpointList,
245 None,
246 cx,
247 )),
248 DebuggerPaneItem::Modules => Box::new(SubView::new(
249 module_list.focus_handle(cx),
250 module_list.clone().into(),
251 DebuggerPaneItem::Modules,
252 None,
253 cx,
254 )),
255 DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
256 loaded_sources.focus_handle(cx),
257 loaded_sources.clone().into(),
258 DebuggerPaneItem::LoadedSources,
259 None,
260 cx,
261 )),
262 DebuggerPaneItem::Console => Box::new(SubView::new(
263 pane.focus_handle(cx),
264 console.clone().into(),
265 DebuggerPaneItem::Console,
266 Some(Box::new({
267 let console = console.clone().downgrade();
268 move |cx| {
269 console
270 .read_with(cx, |console, cx| console.show_indicator(cx))
271 .unwrap_or_default()
272 }
273 })),
274 cx,
275 )),
276 })
277 .collect();
278
279 pane.update(cx, |pane, cx| {
280 let mut active_idx = 0;
281 for (idx, sub_view) in sub_views.into_iter().enumerate() {
282 if serialized_pane
283 .active_item
284 .is_some_and(|active| active == sub_view.read(cx).view_kind())
285 {
286 active_idx = idx;
287 }
288 pane.add_item(sub_view, false, false, None, window, cx);
289 }
290
291 pane.activate_item(active_idx, false, false, window, cx);
292 });
293
294 Some(Member::Pane(pane.clone()))
295 }
296 }
297}