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    pub(crate) fn tab_tooltip(self) -> SharedString {
 65        let tooltip = match self {
 66            DebuggerPaneItem::Console => {
 67                "Displays program output and allows manual input of debugger commands."
 68            }
 69            DebuggerPaneItem::Variables => {
 70                "Shows current values of local and global variables in the current stack frame."
 71            }
 72            DebuggerPaneItem::BreakpointList => "Lists all active breakpoints set in the code.",
 73            DebuggerPaneItem::Frames => {
 74                "Displays the call stack, letting you navigate between function calls."
 75            }
 76            DebuggerPaneItem::Modules => "Shows all modules or libraries loaded by the program.",
 77            DebuggerPaneItem::LoadedSources => {
 78                "Lists all source files currently loaded and used by the debugger."
 79            }
 80            DebuggerPaneItem::Terminal => {
 81                "Provides an interactive terminal session within the debugging environment."
 82            }
 83        };
 84        SharedString::new_static(tooltip)
 85    }
 86}
 87
 88impl From<DebuggerPaneItem> for SharedString {
 89    fn from(item: DebuggerPaneItem) -> Self {
 90        item.to_shared_string()
 91    }
 92}
 93
 94#[derive(Debug, Serialize, Deserialize)]
 95pub(crate) struct SerializedLayout {
 96    pub(crate) panes: SerializedPaneLayout,
 97    pub(crate) dock_axis: Axis,
 98}
 99
100#[derive(Debug, Serialize, Deserialize, Clone)]
101pub(crate) enum SerializedPaneLayout {
102    Pane(SerializedPane),
103    Group {
104        axis: Axis,
105        flexes: Option<Vec<f32>>,
106        children: Vec<SerializedPaneLayout>,
107    },
108}
109
110#[derive(Debug, Serialize, Deserialize, Clone)]
111pub(crate) struct SerializedPane {
112    pub children: Vec<DebuggerPaneItem>,
113    pub active_item: Option<DebuggerPaneItem>,
114}
115
116const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
117
118pub(crate) async fn serialize_pane_layout(
119    adapter_name: DebugAdapterName,
120    pane_group: SerializedLayout,
121) -> anyhow::Result<()> {
122    let serialized_pane_group = serde_json::to_string(&pane_group)
123        .context("Serializing pane group with serde_json as a string")?;
124    KEY_VALUE_STORE
125        .write_kvp(
126            format!("{DEBUGGER_PANEL_PREFIX}-{adapter_name}"),
127            serialized_pane_group,
128        )
129        .await
130}
131
132pub(crate) fn build_serialized_layout(
133    pane_group: &Member,
134    dock_axis: Axis,
135    cx: &App,
136) -> SerializedLayout {
137    SerializedLayout {
138        dock_axis,
139        panes: build_serialized_pane_layout(pane_group, cx),
140    }
141}
142
143pub(crate) fn build_serialized_pane_layout(pane_group: &Member, cx: &App) -> SerializedPaneLayout {
144    match pane_group {
145        Member::Axis(PaneAxis {
146            axis,
147            members,
148            flexes,
149            bounding_boxes: _,
150        }) => SerializedPaneLayout::Group {
151            axis: *axis,
152            children: members
153                .iter()
154                .map(|member| build_serialized_pane_layout(member, cx))
155                .collect::<Vec<_>>(),
156            flexes: Some(flexes.lock().clone()),
157        },
158        Member::Pane(pane_handle) => SerializedPaneLayout::Pane(serialize_pane(pane_handle, cx)),
159    }
160}
161
162fn serialize_pane(pane: &Entity<Pane>, cx: &App) -> SerializedPane {
163    let pane = pane.read(cx);
164    let children = pane
165        .items()
166        .filter_map(|item| {
167            item.act_as::<SubView>(cx)
168                .map(|view| view.read(cx).view_kind())
169        })
170        .collect::<Vec<_>>();
171
172    let active_item = pane
173        .active_item()
174        .and_then(|item| item.act_as::<SubView>(cx))
175        .map(|view| view.read(cx).view_kind());
176
177    SerializedPane {
178        children,
179        active_item,
180    }
181}
182
183pub(crate) async fn get_serialized_layout(
184    adapter_name: impl AsRef<str>,
185) -> Option<SerializedLayout> {
186    let key = format!("{DEBUGGER_PANEL_PREFIX}-{}", adapter_name.as_ref());
187
188    KEY_VALUE_STORE
189        .read_kvp(&key)
190        .log_err()
191        .flatten()
192        .and_then(|value| serde_json::from_str::<SerializedLayout>(&value).ok())
193}
194
195pub(crate) fn deserialize_pane_layout(
196    serialized: SerializedPaneLayout,
197    should_invert: bool,
198    workspace: &WeakEntity<Workspace>,
199    project: &Entity<Project>,
200    stack_frame_list: &Entity<StackFrameList>,
201    variable_list: &Entity<VariableList>,
202    module_list: &Entity<ModuleList>,
203    console: &Entity<Console>,
204    breakpoint_list: &Entity<BreakpointList>,
205    loaded_sources: &Entity<LoadedSourceList>,
206    terminal: &Entity<DebugTerminal>,
207    subscriptions: &mut HashMap<EntityId, Subscription>,
208    window: &mut Window,
209    cx: &mut Context<RunningState>,
210) -> Option<Member> {
211    match serialized {
212        SerializedPaneLayout::Group {
213            axis,
214            flexes,
215            children,
216        } => {
217            let mut members = Vec::new();
218            for child in children {
219                if let Some(new_member) = deserialize_pane_layout(
220                    child,
221                    should_invert,
222                    workspace,
223                    project,
224                    stack_frame_list,
225                    variable_list,
226                    module_list,
227                    console,
228                    breakpoint_list,
229                    loaded_sources,
230                    terminal,
231                    subscriptions,
232                    window,
233                    cx,
234                ) {
235                    members.push(new_member);
236                }
237            }
238
239            if members.is_empty() {
240                return None;
241            }
242
243            if members.len() == 1 {
244                return Some(members.remove(0));
245            }
246
247            Some(Member::Axis(PaneAxis::load(
248                if should_invert { axis.invert() } else { axis },
249                members,
250                flexes.clone(),
251            )))
252        }
253        SerializedPaneLayout::Pane(serialized_pane) => {
254            let pane = running::new_debugger_pane(workspace.clone(), project.clone(), window, cx);
255            subscriptions.insert(
256                pane.entity_id(),
257                cx.subscribe_in(&pane, window, RunningState::handle_pane_event),
258            );
259
260            let sub_views: Vec<_> = serialized_pane
261                .children
262                .iter()
263                .map(|child| match child {
264                    DebuggerPaneItem::Frames => Box::new(SubView::new(
265                        stack_frame_list.focus_handle(cx),
266                        stack_frame_list.clone().into(),
267                        DebuggerPaneItem::Frames,
268                        cx,
269                    )),
270                    DebuggerPaneItem::Variables => Box::new(SubView::new(
271                        variable_list.focus_handle(cx),
272                        variable_list.clone().into(),
273                        DebuggerPaneItem::Variables,
274                        cx,
275                    )),
276                    DebuggerPaneItem::BreakpointList => {
277                        Box::new(SubView::breakpoint_list(breakpoint_list.clone(), cx))
278                    }
279                    DebuggerPaneItem::Modules => Box::new(SubView::new(
280                        module_list.focus_handle(cx),
281                        module_list.clone().into(),
282                        DebuggerPaneItem::Modules,
283                        cx,
284                    )),
285                    DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
286                        loaded_sources.focus_handle(cx),
287                        loaded_sources.clone().into(),
288                        DebuggerPaneItem::LoadedSources,
289                        cx,
290                    )),
291                    DebuggerPaneItem::Console => {
292                        let view = SubView::console(console.clone(), cx);
293                        Box::new(view)
294                    }
295                    DebuggerPaneItem::Terminal => Box::new(SubView::new(
296                        terminal.focus_handle(cx),
297                        terminal.clone().into(),
298                        DebuggerPaneItem::Terminal,
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}