persistence.rs

  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}