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