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