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