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                        None,
269                        cx,
270                    )),
271                    DebuggerPaneItem::Variables => Box::new(SubView::new(
272                        variable_list.focus_handle(cx),
273                        variable_list.clone().into(),
274                        DebuggerPaneItem::Variables,
275                        None,
276                        cx,
277                    )),
278                    DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
279                        breakpoint_list.focus_handle(cx),
280                        breakpoint_list.clone().into(),
281                        DebuggerPaneItem::BreakpointList,
282                        None,
283                        cx,
284                    )),
285                    DebuggerPaneItem::Modules => Box::new(SubView::new(
286                        module_list.focus_handle(cx),
287                        module_list.clone().into(),
288                        DebuggerPaneItem::Modules,
289                        None,
290                        cx,
291                    )),
292                    DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
293                        loaded_sources.focus_handle(cx),
294                        loaded_sources.clone().into(),
295                        DebuggerPaneItem::LoadedSources,
296                        None,
297                        cx,
298                    )),
299                    DebuggerPaneItem::Console => Box::new(SubView::new(
300                        console.focus_handle(cx),
301                        console.clone().into(),
302                        DebuggerPaneItem::Console,
303                        Some(Box::new({
304                            let console = console.clone().downgrade();
305                            move |cx| {
306                                console
307                                    .read_with(cx, |console, cx| console.show_indicator(cx))
308                                    .unwrap_or_default()
309                            }
310                        })),
311                        cx,
312                    )),
313                    DebuggerPaneItem::Terminal => Box::new(SubView::new(
314                        terminal.focus_handle(cx),
315                        terminal.clone().into(),
316                        DebuggerPaneItem::Terminal,
317                        None,
318                        cx,
319                    )),
320                })
321                .collect();
322
323            pane.update(cx, |pane, cx| {
324                let mut active_idx = 0;
325                for (idx, sub_view) in sub_views.into_iter().enumerate() {
326                    if serialized_pane
327                        .active_item
328                        .is_some_and(|active| active == sub_view.read(cx).view_kind())
329                    {
330                        active_idx = idx;
331                    }
332                    pane.add_item(sub_view, false, false, None, window, cx);
333                }
334
335                pane.activate_item(active_idx, false, false, window, cx);
336            });
337
338            Some(Member::Pane(pane.clone()))
339        }
340    }
341}
342
343#[cfg(test)]
344impl SerializedPaneLayout {
345    pub(crate) fn in_order(&self) -> Vec<SerializedPaneLayout> {
346        let mut panes = vec![];
347
348        Self::inner_in_order(&self, &mut panes);
349        panes
350    }
351
352    fn inner_in_order(&self, panes: &mut Vec<SerializedPaneLayout>) {
353        match self {
354            SerializedPaneLayout::Pane(_) => panes.push((*self).clone()),
355            SerializedPaneLayout::Group {
356                axis: _,
357                flexes: _,
358                children,
359            } => {
360                for child in children {
361                    child.inner_in_order(panes);
362                }
363            }
364        }
365    }
366}