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 SerializedAxis(pub Axis);
73
74#[derive(Debug, Serialize, Deserialize)]
75pub(crate) enum SerializedPaneLayout {
76 Pane(SerializedPane),
77 Group {
78 axis: SerializedAxis,
79 flexes: Option<Vec<f32>>,
80 children: Vec<SerializedPaneLayout>,
81 },
82}
83
84#[derive(Debug, Serialize, Deserialize)]
85pub(crate) struct SerializedPane {
86 pub children: Vec<DebuggerPaneItem>,
87 pub active_item: Option<DebuggerPaneItem>,
88}
89
90const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
91
92pub(crate) async fn serialize_pane_layout(
93 adapter_name: DebugAdapterName,
94 pane_group: SerializedPaneLayout,
95) -> anyhow::Result<()> {
96 if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) {
97 KEY_VALUE_STORE
98 .write_kvp(
99 format!("{DEBUGGER_PANEL_PREFIX}-{adapter_name}"),
100 serialized_pane_group,
101 )
102 .await
103 } else {
104 Err(anyhow::anyhow!(
105 "Failed to serialize pane group with serde_json as a string"
106 ))
107 }
108}
109
110pub(crate) fn build_serialized_pane_layout(
111 pane_group: &Member,
112 cx: &mut App,
113) -> SerializedPaneLayout {
114 match pane_group {
115 Member::Axis(PaneAxis {
116 axis,
117 members,
118 flexes,
119 bounding_boxes: _,
120 }) => SerializedPaneLayout::Group {
121 axis: SerializedAxis(*axis),
122 children: members
123 .iter()
124 .map(|member| build_serialized_pane_layout(member, cx))
125 .collect::<Vec<_>>(),
126 flexes: Some(flexes.lock().clone()),
127 },
128 Member::Pane(pane_handle) => SerializedPaneLayout::Pane(serialize_pane(pane_handle, cx)),
129 }
130}
131
132fn serialize_pane(pane: &Entity<Pane>, cx: &mut App) -> SerializedPane {
133 let pane = pane.read(cx);
134 let children = pane
135 .items()
136 .filter_map(|item| {
137 item.act_as::<SubView>(cx)
138 .map(|view| view.read(cx).view_kind())
139 })
140 .collect::<Vec<_>>();
141
142 let active_item = pane
143 .active_item()
144 .and_then(|item| item.act_as::<SubView>(cx))
145 .map(|view| view.read(cx).view_kind());
146
147 SerializedPane {
148 children,
149 active_item,
150 }
151}
152
153pub(crate) async fn get_serialized_pane_layout(
154 adapter_name: impl AsRef<str>,
155) -> Option<SerializedPaneLayout> {
156 let key = format!("{DEBUGGER_PANEL_PREFIX}-{}", adapter_name.as_ref());
157
158 KEY_VALUE_STORE
159 .read_kvp(&key)
160 .log_err()
161 .flatten()
162 .and_then(|value| serde_json::from_str::<SerializedPaneLayout>(&value).ok())
163}
164
165pub(crate) fn deserialize_pane_layout(
166 serialized: SerializedPaneLayout,
167 workspace: &WeakEntity<Workspace>,
168 project: &Entity<Project>,
169 stack_frame_list: &Entity<StackFrameList>,
170 variable_list: &Entity<VariableList>,
171 module_list: &Entity<ModuleList>,
172 console: &Entity<Console>,
173 breakpoint_list: &Entity<BreakpointList>,
174 loaded_sources: &Entity<LoadedSourceList>,
175 terminal: &Entity<DebugTerminal>,
176 subscriptions: &mut HashMap<EntityId, Subscription>,
177 window: &mut Window,
178 cx: &mut Context<RunningState>,
179) -> Option<Member> {
180 match serialized {
181 SerializedPaneLayout::Group {
182 axis,
183 flexes,
184 children,
185 } => {
186 let mut members = Vec::new();
187 for child in children {
188 if let Some(new_member) = deserialize_pane_layout(
189 child,
190 workspace,
191 project,
192 stack_frame_list,
193 variable_list,
194 module_list,
195 console,
196 breakpoint_list,
197 loaded_sources,
198 terminal,
199 subscriptions,
200 window,
201 cx,
202 ) {
203 members.push(new_member);
204 }
205 }
206
207 if members.is_empty() {
208 return None;
209 }
210
211 if members.len() == 1 {
212 return Some(members.remove(0));
213 }
214
215 Some(Member::Axis(PaneAxis::load(
216 axis.0,
217 members,
218 flexes.clone(),
219 )))
220 }
221 SerializedPaneLayout::Pane(serialized_pane) => {
222 let pane = running::new_debugger_pane(workspace.clone(), project.clone(), window, cx);
223 subscriptions.insert(
224 pane.entity_id(),
225 cx.subscribe_in(&pane, window, RunningState::handle_pane_event),
226 );
227
228 let sub_views: Vec<_> = serialized_pane
229 .children
230 .iter()
231 .map(|child| match child {
232 DebuggerPaneItem::Frames => Box::new(SubView::new(
233 stack_frame_list.focus_handle(cx),
234 stack_frame_list.clone().into(),
235 DebuggerPaneItem::Frames,
236 None,
237 cx,
238 )),
239 DebuggerPaneItem::Variables => Box::new(SubView::new(
240 variable_list.focus_handle(cx),
241 variable_list.clone().into(),
242 DebuggerPaneItem::Variables,
243 None,
244 cx,
245 )),
246 DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
247 breakpoint_list.focus_handle(cx),
248 breakpoint_list.clone().into(),
249 DebuggerPaneItem::BreakpointList,
250 None,
251 cx,
252 )),
253 DebuggerPaneItem::Modules => Box::new(SubView::new(
254 module_list.focus_handle(cx),
255 module_list.clone().into(),
256 DebuggerPaneItem::Modules,
257 None,
258 cx,
259 )),
260 DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
261 loaded_sources.focus_handle(cx),
262 loaded_sources.clone().into(),
263 DebuggerPaneItem::LoadedSources,
264 None,
265 cx,
266 )),
267 DebuggerPaneItem::Console => Box::new(SubView::new(
268 pane.focus_handle(cx),
269 console.clone().into(),
270 DebuggerPaneItem::Console,
271 Some(Box::new({
272 let console = console.clone().downgrade();
273 move |cx| {
274 console
275 .read_with(cx, |console, cx| console.show_indicator(cx))
276 .unwrap_or_default()
277 }
278 })),
279 cx,
280 )),
281 DebuggerPaneItem::Terminal => Box::new(SubView::new(
282 pane.focus_handle(cx),
283 terminal.clone().into(),
284 DebuggerPaneItem::Terminal,
285 None,
286 cx,
287 )),
288 })
289 .collect();
290
291 pane.update(cx, |pane, cx| {
292 let mut active_idx = 0;
293 for (idx, sub_view) in sub_views.into_iter().enumerate() {
294 if serialized_pane
295 .active_item
296 .is_some_and(|active| active == sub_view.read(cx).view_kind())
297 {
298 active_idx = idx;
299 }
300 pane.add_item(sub_view, false, false, None, window, cx);
301 }
302
303 pane.activate_item(active_idx, false, false, window, cx);
304 });
305
306 Some(Member::Pane(pane.clone()))
307 }
308 }
309}