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) 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) 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 state,
156 }) => SerializedPaneLayout::Group {
157 axis: *axis,
158 children: members
159 .iter()
160 .map(|member| build_serialized_pane_layout(member, cx))
161 .collect::<Vec<_>>(),
162 flexes: Some(state.flexes()),
163 },
164 Member::Pane(pane_handle) => SerializedPaneLayout::Pane(serialize_pane(pane_handle, cx)),
165 }
166}
167
168fn serialize_pane(pane: &Entity<Pane>, cx: &App) -> SerializedPane {
169 let pane = pane.read(cx);
170 let children = pane
171 .items()
172 .filter_map(|item| {
173 item.act_as::<SubView>(cx)
174 .map(|view| view.read(cx).view_kind())
175 })
176 .collect::<Vec<_>>();
177
178 let active_item = pane
179 .active_item()
180 .and_then(|item| item.act_as::<SubView>(cx))
181 .map(|view| view.read(cx).view_kind());
182
183 SerializedPane {
184 children,
185 active_item,
186 }
187}
188
189pub(crate) async fn get_serialized_layout(
190 adapter_name: impl AsRef<str>,
191) -> Option<SerializedLayout> {
192 let key = format!("{DEBUGGER_PANEL_PREFIX}-{}", adapter_name.as_ref());
193
194 KEY_VALUE_STORE
195 .read_kvp(&key)
196 .log_err()
197 .flatten()
198 .and_then(|value| serde_json::from_str::<SerializedLayout>(&value).ok())
199}
200
201pub(crate) fn deserialize_pane_layout(
202 serialized: SerializedPaneLayout,
203 should_invert: bool,
204 workspace: &WeakEntity<Workspace>,
205 project: &Entity<Project>,
206 stack_frame_list: &Entity<StackFrameList>,
207 variable_list: &Entity<VariableList>,
208 module_list: &Entity<ModuleList>,
209 console: &Entity<Console>,
210 breakpoint_list: &Entity<BreakpointList>,
211 loaded_sources: &Entity<LoadedSourceList>,
212 terminal: &Entity<DebugTerminal>,
213 memory_view: &Entity<MemoryView>,
214 subscriptions: &mut HashMap<EntityId, Subscription>,
215 window: &mut Window,
216 cx: &mut Context<RunningState>,
217) -> Option<Member> {
218 match serialized {
219 SerializedPaneLayout::Group {
220 axis,
221 flexes,
222 children,
223 } => {
224 let mut members = Vec::new();
225 for child in children {
226 if let Some(new_member) = deserialize_pane_layout(
227 child,
228 should_invert,
229 workspace,
230 project,
231 stack_frame_list,
232 variable_list,
233 module_list,
234 console,
235 breakpoint_list,
236 loaded_sources,
237 terminal,
238 memory_view,
239 subscriptions,
240 window,
241 cx,
242 ) {
243 members.push(new_member);
244 }
245 }
246
247 if members.is_empty() {
248 return None;
249 }
250
251 if members.len() == 1 {
252 return Some(members.remove(0));
253 }
254
255 Some(Member::Axis(PaneAxis::load(
256 if should_invert { axis.invert() } else { axis },
257 members,
258 flexes,
259 )))
260 }
261 SerializedPaneLayout::Pane(serialized_pane) => {
262 let pane = running::new_debugger_pane(workspace.clone(), project.clone(), window, cx);
263 subscriptions.insert(
264 pane.entity_id(),
265 cx.subscribe_in(&pane, window, RunningState::handle_pane_event),
266 );
267 let running_state = cx.weak_entity();
268 let pane_handle = pane.downgrade();
269
270 let sub_views: Vec<_> = serialized_pane
271 .children
272 .iter()
273 .map(|child| match child {
274 DebuggerPaneItem::Frames => Box::new(SubView::stack_frame_list(
275 stack_frame_list.clone(),
276 running_state.clone(),
277 pane_handle.clone(),
278 cx,
279 )),
280 DebuggerPaneItem::Variables => Box::new(SubView::new(
281 variable_list.focus_handle(cx),
282 variable_list.clone().into(),
283 DebuggerPaneItem::Variables,
284 running_state.clone(),
285 pane_handle.clone(),
286 cx,
287 )),
288 DebuggerPaneItem::BreakpointList => Box::new(SubView::breakpoint_list(
289 breakpoint_list.clone(),
290 running_state.clone(),
291 pane_handle.clone(),
292 cx,
293 )),
294 DebuggerPaneItem::Modules => Box::new(SubView::new(
295 module_list.focus_handle(cx),
296 module_list.clone().into(),
297 DebuggerPaneItem::Modules,
298 running_state.clone(),
299 pane_handle.clone(),
300 cx,
301 )),
302 DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
303 loaded_sources.focus_handle(cx),
304 loaded_sources.clone().into(),
305 DebuggerPaneItem::LoadedSources,
306 running_state.clone(),
307 pane_handle.clone(),
308 cx,
309 )),
310 DebuggerPaneItem::Console => {
311 let view = SubView::console(
312 console.clone(),
313 running_state.clone(),
314 pane_handle.clone(),
315 cx,
316 );
317 Box::new(view)
318 }
319 DebuggerPaneItem::Terminal => Box::new(SubView::new(
320 terminal.focus_handle(cx),
321 terminal.clone().into(),
322 DebuggerPaneItem::Terminal,
323 running_state.clone(),
324 pane_handle.clone(),
325 cx,
326 )),
327 DebuggerPaneItem::MemoryView => Box::new(SubView::new(
328 memory_view.focus_handle(cx),
329 memory_view.clone().into(),
330 DebuggerPaneItem::MemoryView,
331 running_state.clone(),
332 pane_handle.clone(),
333 cx,
334 )),
335 })
336 .collect();
337
338 pane.update(cx, |pane, cx| {
339 let mut active_idx = 0;
340 for (idx, sub_view) in sub_views.into_iter().enumerate() {
341 if serialized_pane
342 .active_item
343 .is_some_and(|active| active == sub_view.read(cx).view_kind())
344 {
345 active_idx = idx;
346 }
347 pane.add_item(sub_view, false, false, None, window, cx);
348 }
349
350 pane.activate_item(active_idx, false, false, window, cx);
351 });
352
353 Some(Member::Pane(pane.clone()))
354 }
355 }
356}
357
358#[cfg(test)]
359impl SerializedPaneLayout {
360 pub(crate) fn in_order(&self) -> Vec<SerializedPaneLayout> {
361 let mut panes = vec![];
362
363 Self::inner_in_order(self, &mut panes);
364 panes
365 }
366
367 fn inner_in_order(&self, panes: &mut Vec<SerializedPaneLayout>) {
368 match self {
369 SerializedPaneLayout::Pane(_) => panes.push((*self).clone()),
370 SerializedPaneLayout::Group {
371 axis: _,
372 flexes: _,
373 children,
374 } => {
375 for child in children {
376 child.inner_in_order(panes);
377 }
378 }
379 }
380 }
381}