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 pub(crate) fn tab_tooltip(self) -> SharedString {
65 let tooltip = match self {
66 DebuggerPaneItem::Console => {
67 "Displays program output and allows manual input of debugger commands."
68 }
69 DebuggerPaneItem::Variables => {
70 "Shows current values of local and global variables in the current stack frame."
71 }
72 DebuggerPaneItem::BreakpointList => "Lists all active breakpoints set in the code.",
73 DebuggerPaneItem::Frames => {
74 "Displays the call stack, letting you navigate between function calls."
75 }
76 DebuggerPaneItem::Modules => "Shows all modules or libraries loaded by the program.",
77 DebuggerPaneItem::LoadedSources => {
78 "Lists all source files currently loaded and used by the debugger."
79 }
80 DebuggerPaneItem::Terminal => {
81 "Provides an interactive terminal session within the debugging environment."
82 }
83 };
84 SharedString::new_static(tooltip)
85 }
86}
87
88impl From<DebuggerPaneItem> for SharedString {
89 fn from(item: DebuggerPaneItem) -> Self {
90 item.to_shared_string()
91 }
92}
93
94#[derive(Debug, Serialize, Deserialize)]
95pub(crate) struct SerializedLayout {
96 pub(crate) panes: SerializedPaneLayout,
97 pub(crate) dock_axis: Axis,
98}
99
100#[derive(Debug, Serialize, Deserialize, Clone)]
101pub(crate) enum SerializedPaneLayout {
102 Pane(SerializedPane),
103 Group {
104 axis: Axis,
105 flexes: Option<Vec<f32>>,
106 children: Vec<SerializedPaneLayout>,
107 },
108}
109
110#[derive(Debug, Serialize, Deserialize, Clone)]
111pub(crate) struct SerializedPane {
112 pub children: Vec<DebuggerPaneItem>,
113 pub active_item: Option<DebuggerPaneItem>,
114}
115
116const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
117
118pub(crate) async fn serialize_pane_layout(
119 adapter_name: DebugAdapterName,
120 pane_group: SerializedLayout,
121) -> anyhow::Result<()> {
122 let serialized_pane_group = serde_json::to_string(&pane_group)
123 .context("Serializing pane group with serde_json as a string")?;
124 KEY_VALUE_STORE
125 .write_kvp(
126 format!("{DEBUGGER_PANEL_PREFIX}-{adapter_name}"),
127 serialized_pane_group,
128 )
129 .await
130}
131
132pub(crate) fn build_serialized_layout(
133 pane_group: &Member,
134 dock_axis: Axis,
135 cx: &App,
136) -> SerializedLayout {
137 SerializedLayout {
138 dock_axis,
139 panes: build_serialized_pane_layout(pane_group, cx),
140 }
141}
142
143pub(crate) fn build_serialized_pane_layout(pane_group: &Member, cx: &App) -> SerializedPaneLayout {
144 match pane_group {
145 Member::Axis(PaneAxis {
146 axis,
147 members,
148 flexes,
149 bounding_boxes: _,
150 }) => SerializedPaneLayout::Group {
151 axis: *axis,
152 children: members
153 .iter()
154 .map(|member| build_serialized_pane_layout(member, cx))
155 .collect::<Vec<_>>(),
156 flexes: Some(flexes.lock().clone()),
157 },
158 Member::Pane(pane_handle) => SerializedPaneLayout::Pane(serialize_pane(pane_handle, cx)),
159 }
160}
161
162fn serialize_pane(pane: &Entity<Pane>, cx: &App) -> SerializedPane {
163 let pane = pane.read(cx);
164 let children = pane
165 .items()
166 .filter_map(|item| {
167 item.act_as::<SubView>(cx)
168 .map(|view| view.read(cx).view_kind())
169 })
170 .collect::<Vec<_>>();
171
172 let active_item = pane
173 .active_item()
174 .and_then(|item| item.act_as::<SubView>(cx))
175 .map(|view| view.read(cx).view_kind());
176
177 SerializedPane {
178 children,
179 active_item,
180 }
181}
182
183pub(crate) async fn get_serialized_layout(
184 adapter_name: impl AsRef<str>,
185) -> Option<SerializedLayout> {
186 let key = format!("{DEBUGGER_PANEL_PREFIX}-{}", adapter_name.as_ref());
187
188 KEY_VALUE_STORE
189 .read_kvp(&key)
190 .log_err()
191 .flatten()
192 .and_then(|value| serde_json::from_str::<SerializedLayout>(&value).ok())
193}
194
195pub(crate) fn deserialize_pane_layout(
196 serialized: SerializedPaneLayout,
197 should_invert: bool,
198 workspace: &WeakEntity<Workspace>,
199 project: &Entity<Project>,
200 stack_frame_list: &Entity<StackFrameList>,
201 variable_list: &Entity<VariableList>,
202 module_list: &Entity<ModuleList>,
203 console: &Entity<Console>,
204 breakpoint_list: &Entity<BreakpointList>,
205 loaded_sources: &Entity<LoadedSourceList>,
206 terminal: &Entity<DebugTerminal>,
207 subscriptions: &mut HashMap<EntityId, Subscription>,
208 window: &mut Window,
209 cx: &mut Context<RunningState>,
210) -> Option<Member> {
211 match serialized {
212 SerializedPaneLayout::Group {
213 axis,
214 flexes,
215 children,
216 } => {
217 let mut members = Vec::new();
218 for child in children {
219 if let Some(new_member) = deserialize_pane_layout(
220 child,
221 should_invert,
222 workspace,
223 project,
224 stack_frame_list,
225 variable_list,
226 module_list,
227 console,
228 breakpoint_list,
229 loaded_sources,
230 terminal,
231 subscriptions,
232 window,
233 cx,
234 ) {
235 members.push(new_member);
236 }
237 }
238
239 if members.is_empty() {
240 return None;
241 }
242
243 if members.len() == 1 {
244 return Some(members.remove(0));
245 }
246
247 Some(Member::Axis(PaneAxis::load(
248 if should_invert { axis.invert() } else { axis },
249 members,
250 flexes.clone(),
251 )))
252 }
253 SerializedPaneLayout::Pane(serialized_pane) => {
254 let pane = running::new_debugger_pane(workspace.clone(), project.clone(), window, cx);
255 subscriptions.insert(
256 pane.entity_id(),
257 cx.subscribe_in(&pane, window, RunningState::handle_pane_event),
258 );
259
260 let sub_views: Vec<_> = serialized_pane
261 .children
262 .iter()
263 .map(|child| match child {
264 DebuggerPaneItem::Frames => Box::new(SubView::new(
265 stack_frame_list.focus_handle(cx),
266 stack_frame_list.clone().into(),
267 DebuggerPaneItem::Frames,
268 None,
269 cx,
270 )),
271 DebuggerPaneItem::Variables => Box::new(SubView::new(
272 variable_list.focus_handle(cx),
273 variable_list.clone().into(),
274 DebuggerPaneItem::Variables,
275 None,
276 cx,
277 )),
278 DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
279 breakpoint_list.focus_handle(cx),
280 breakpoint_list.clone().into(),
281 DebuggerPaneItem::BreakpointList,
282 None,
283 cx,
284 )),
285 DebuggerPaneItem::Modules => Box::new(SubView::new(
286 module_list.focus_handle(cx),
287 module_list.clone().into(),
288 DebuggerPaneItem::Modules,
289 None,
290 cx,
291 )),
292 DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
293 loaded_sources.focus_handle(cx),
294 loaded_sources.clone().into(),
295 DebuggerPaneItem::LoadedSources,
296 None,
297 cx,
298 )),
299 DebuggerPaneItem::Console => Box::new(SubView::new(
300 console.focus_handle(cx),
301 console.clone().into(),
302 DebuggerPaneItem::Console,
303 Some(Box::new({
304 let console = console.clone().downgrade();
305 move |cx| {
306 console
307 .read_with(cx, |console, cx| console.show_indicator(cx))
308 .unwrap_or_default()
309 }
310 })),
311 cx,
312 )),
313 DebuggerPaneItem::Terminal => Box::new(SubView::new(
314 terminal.focus_handle(cx),
315 terminal.clone().into(),
316 DebuggerPaneItem::Terminal,
317 None,
318 cx,
319 )),
320 })
321 .collect();
322
323 pane.update(cx, |pane, cx| {
324 let mut active_idx = 0;
325 for (idx, sub_view) in sub_views.into_iter().enumerate() {
326 if serialized_pane
327 .active_item
328 .is_some_and(|active| active == sub_view.read(cx).view_kind())
329 {
330 active_idx = idx;
331 }
332 pane.add_item(sub_view, false, false, None, window, cx);
333 }
334
335 pane.activate_item(active_idx, false, false, window, cx);
336 });
337
338 Some(Member::Pane(pane.clone()))
339 }
340 }
341}
342
343#[cfg(test)]
344impl SerializedPaneLayout {
345 pub(crate) fn in_order(&self) -> Vec<SerializedPaneLayout> {
346 let mut panes = vec![];
347
348 Self::inner_in_order(&self, &mut panes);
349 panes
350 }
351
352 fn inner_in_order(&self, panes: &mut Vec<SerializedPaneLayout>) {
353 match self {
354 SerializedPaneLayout::Pane(_) => panes.push((*self).clone()),
355 SerializedPaneLayout::Group {
356 axis: _,
357 flexes: _,
358 children,
359 } => {
360 for child in children {
361 child.inner_in_order(panes);
362 }
363 }
364 }
365 }
366}