persistence.rs

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