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) 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}