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