ZIm/crates/debugger_ui/src/persistence.rs
Piotr Osiewicz 00fe195416
debugger: Move breakpoint management to the pane strip (#33062)
Closes #ISSUE

Release Notes:

- debugger: Moved "remove breakpoint" button to the top of a breakpoint
list"
2025-06-19 22:29:15 +02:00

347 lines
12 KiB
Rust

use anyhow::Context as _;
use collections::HashMap;
use dap::{Capabilities, adapters::DebugAdapterName};
use db::kvp::KEY_VALUE_STORE;
use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window};
use project::Project;
use serde::{Deserialize, Serialize};
use ui::{App, SharedString};
use util::ResultExt;
use workspace::{Member, Pane, PaneAxis, Workspace};
use crate::session::running::{
self, DebugTerminal, RunningState, SubView, breakpoint_list::BreakpointList, console::Console,
loaded_source_list::LoadedSourceList, module_list::ModuleList,
stack_frame_list::StackFrameList, variable_list::VariableList,
};
#[derive(Clone, Hash, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) enum DebuggerPaneItem {
Console,
Variables,
BreakpointList,
Frames,
Modules,
LoadedSources,
Terminal,
}
impl DebuggerPaneItem {
pub(crate) fn all() -> &'static [DebuggerPaneItem] {
static VARIANTS: &[DebuggerPaneItem] = &[
DebuggerPaneItem::Console,
DebuggerPaneItem::Variables,
DebuggerPaneItem::BreakpointList,
DebuggerPaneItem::Frames,
DebuggerPaneItem::Modules,
DebuggerPaneItem::LoadedSources,
DebuggerPaneItem::Terminal,
];
VARIANTS
}
pub(crate) fn is_supported(&self, capabilities: &Capabilities) -> bool {
match self {
DebuggerPaneItem::Modules => capabilities.supports_modules_request.unwrap_or_default(),
DebuggerPaneItem::LoadedSources => capabilities
.supports_loaded_sources_request
.unwrap_or_default(),
_ => true,
}
}
pub(crate) fn to_shared_string(self) -> SharedString {
match self {
DebuggerPaneItem::Console => SharedString::new_static("Console"),
DebuggerPaneItem::Variables => SharedString::new_static("Variables"),
DebuggerPaneItem::BreakpointList => SharedString::new_static("Breakpoints"),
DebuggerPaneItem::Frames => SharedString::new_static("Frames"),
DebuggerPaneItem::Modules => SharedString::new_static("Modules"),
DebuggerPaneItem::LoadedSources => SharedString::new_static("Sources"),
DebuggerPaneItem::Terminal => SharedString::new_static("Terminal"),
}
}
pub(crate) fn tab_tooltip(self) -> SharedString {
let tooltip = match self {
DebuggerPaneItem::Console => {
"Displays program output and allows manual input of debugger commands."
}
DebuggerPaneItem::Variables => {
"Shows current values of local and global variables in the current stack frame."
}
DebuggerPaneItem::BreakpointList => "Lists all active breakpoints set in the code.",
DebuggerPaneItem::Frames => {
"Displays the call stack, letting you navigate between function calls."
}
DebuggerPaneItem::Modules => "Shows all modules or libraries loaded by the program.",
DebuggerPaneItem::LoadedSources => {
"Lists all source files currently loaded and used by the debugger."
}
DebuggerPaneItem::Terminal => {
"Provides an interactive terminal session within the debugging environment."
}
};
SharedString::new_static(tooltip)
}
}
impl From<DebuggerPaneItem> for SharedString {
fn from(item: DebuggerPaneItem) -> Self {
item.to_shared_string()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct SerializedLayout {
pub(crate) panes: SerializedPaneLayout,
pub(crate) dock_axis: Axis,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) enum SerializedPaneLayout {
Pane(SerializedPane),
Group {
axis: Axis,
flexes: Option<Vec<f32>>,
children: Vec<SerializedPaneLayout>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct SerializedPane {
pub children: Vec<DebuggerPaneItem>,
pub active_item: Option<DebuggerPaneItem>,
}
const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
pub(crate) async fn serialize_pane_layout(
adapter_name: DebugAdapterName,
pane_group: SerializedLayout,
) -> anyhow::Result<()> {
let serialized_pane_group = serde_json::to_string(&pane_group)
.context("Serializing pane group with serde_json as a string")?;
KEY_VALUE_STORE
.write_kvp(
format!("{DEBUGGER_PANEL_PREFIX}-{adapter_name}"),
serialized_pane_group,
)
.await
}
pub(crate) fn build_serialized_layout(
pane_group: &Member,
dock_axis: Axis,
cx: &App,
) -> SerializedLayout {
SerializedLayout {
dock_axis,
panes: build_serialized_pane_layout(pane_group, cx),
}
}
pub(crate) fn build_serialized_pane_layout(pane_group: &Member, cx: &App) -> SerializedPaneLayout {
match pane_group {
Member::Axis(PaneAxis {
axis,
members,
flexes,
bounding_boxes: _,
}) => SerializedPaneLayout::Group {
axis: *axis,
children: members
.iter()
.map(|member| build_serialized_pane_layout(member, cx))
.collect::<Vec<_>>(),
flexes: Some(flexes.lock().clone()),
},
Member::Pane(pane_handle) => SerializedPaneLayout::Pane(serialize_pane(pane_handle, cx)),
}
}
fn serialize_pane(pane: &Entity<Pane>, cx: &App) -> SerializedPane {
let pane = pane.read(cx);
let children = pane
.items()
.filter_map(|item| {
item.act_as::<SubView>(cx)
.map(|view| view.read(cx).view_kind())
})
.collect::<Vec<_>>();
let active_item = pane
.active_item()
.and_then(|item| item.act_as::<SubView>(cx))
.map(|view| view.read(cx).view_kind());
SerializedPane {
children,
active_item,
}
}
pub(crate) async fn get_serialized_layout(
adapter_name: impl AsRef<str>,
) -> Option<SerializedLayout> {
let key = format!("{DEBUGGER_PANEL_PREFIX}-{}", adapter_name.as_ref());
KEY_VALUE_STORE
.read_kvp(&key)
.log_err()
.flatten()
.and_then(|value| serde_json::from_str::<SerializedLayout>(&value).ok())
}
pub(crate) fn deserialize_pane_layout(
serialized: SerializedPaneLayout,
should_invert: bool,
workspace: &WeakEntity<Workspace>,
project: &Entity<Project>,
stack_frame_list: &Entity<StackFrameList>,
variable_list: &Entity<VariableList>,
module_list: &Entity<ModuleList>,
console: &Entity<Console>,
breakpoint_list: &Entity<BreakpointList>,
loaded_sources: &Entity<LoadedSourceList>,
terminal: &Entity<DebugTerminal>,
subscriptions: &mut HashMap<EntityId, Subscription>,
window: &mut Window,
cx: &mut Context<RunningState>,
) -> Option<Member> {
match serialized {
SerializedPaneLayout::Group {
axis,
flexes,
children,
} => {
let mut members = Vec::new();
for child in children {
if let Some(new_member) = deserialize_pane_layout(
child,
should_invert,
workspace,
project,
stack_frame_list,
variable_list,
module_list,
console,
breakpoint_list,
loaded_sources,
terminal,
subscriptions,
window,
cx,
) {
members.push(new_member);
}
}
if members.is_empty() {
return None;
}
if members.len() == 1 {
return Some(members.remove(0));
}
Some(Member::Axis(PaneAxis::load(
if should_invert { axis.invert() } else { axis },
members,
flexes.clone(),
)))
}
SerializedPaneLayout::Pane(serialized_pane) => {
let pane = running::new_debugger_pane(workspace.clone(), project.clone(), window, cx);
subscriptions.insert(
pane.entity_id(),
cx.subscribe_in(&pane, window, RunningState::handle_pane_event),
);
let sub_views: Vec<_> = serialized_pane
.children
.iter()
.map(|child| match child {
DebuggerPaneItem::Frames => Box::new(SubView::new(
stack_frame_list.focus_handle(cx),
stack_frame_list.clone().into(),
DebuggerPaneItem::Frames,
cx,
)),
DebuggerPaneItem::Variables => Box::new(SubView::new(
variable_list.focus_handle(cx),
variable_list.clone().into(),
DebuggerPaneItem::Variables,
cx,
)),
DebuggerPaneItem::BreakpointList => {
Box::new(SubView::breakpoint_list(breakpoint_list.clone(), cx))
}
DebuggerPaneItem::Modules => Box::new(SubView::new(
module_list.focus_handle(cx),
module_list.clone().into(),
DebuggerPaneItem::Modules,
cx,
)),
DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
loaded_sources.focus_handle(cx),
loaded_sources.clone().into(),
DebuggerPaneItem::LoadedSources,
cx,
)),
DebuggerPaneItem::Console => {
let view = SubView::console(console.clone(), cx);
Box::new(view)
}
DebuggerPaneItem::Terminal => Box::new(SubView::new(
terminal.focus_handle(cx),
terminal.clone().into(),
DebuggerPaneItem::Terminal,
cx,
)),
})
.collect();
pane.update(cx, |pane, cx| {
let mut active_idx = 0;
for (idx, sub_view) in sub_views.into_iter().enumerate() {
if serialized_pane
.active_item
.is_some_and(|active| active == sub_view.read(cx).view_kind())
{
active_idx = idx;
}
pane.add_item(sub_view, false, false, None, window, cx);
}
pane.activate_item(active_idx, false, false, window, cx);
});
Some(Member::Pane(pane.clone()))
}
}
}
#[cfg(test)]
impl SerializedPaneLayout {
pub(crate) fn in_order(&self) -> Vec<SerializedPaneLayout> {
let mut panes = vec![];
Self::inner_in_order(&self, &mut panes);
panes
}
fn inner_in_order(&self, panes: &mut Vec<SerializedPaneLayout>) {
match self {
SerializedPaneLayout::Pane(_) => panes.push((*self).clone()),
SerializedPaneLayout::Group {
axis: _,
flexes: _,
children,
} => {
for child in children {
child.inner_in_order(panes);
}
}
}
}
}