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