Cargo.lock 🔗
@@ -4177,6 +4177,7 @@ dependencies = [
"collections",
"command_palette_hooks",
"dap",
+ "db",
"editor",
"env_logger 0.11.8",
"feature_flags",
Anthony Eid , Piotr Osiewicz , and Cole Miller created
This PR makes a debugger's pane layout persistent across session's that
use the same debug adapter.
Release Notes:
- N/A
---------
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
Cargo.lock | 1
crates/db/src/kvp.rs | 1
crates/debugger_ui/Cargo.toml | 1
crates/debugger_ui/src/debugger_panel.rs | 68 +
crates/debugger_ui/src/debugger_ui.rs | 1
crates/debugger_ui/src/persistence.rs | 259 ++++++
crates/debugger_ui/src/session.rs | 3
crates/debugger_ui/src/session/running.rs | 339 +++++---
crates/debugger_ui/src/session/running/breakpoint_list.rs | 2
crates/debugger_ui/src/tests.rs | 1
crates/debugger_ui/src/tests/debugger_panel.rs | 6
crates/debugger_ui/src/tests/variable_list.rs | 2
crates/project/src/debugger/session.rs | 12
13 files changed, 548 insertions(+), 148 deletions(-)
@@ -4177,6 +4177,7 @@ dependencies = [
"collections",
"command_palette_hooks",
"dap",
+ "db",
"editor",
"env_logger 0.11.8",
"feature_flags",
@@ -1,6 +1,7 @@
use sqlez_macros::sql;
use crate::{define_connection, query};
+pub static DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
&[sql!(
@@ -28,6 +28,7 @@ client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
dap.workspace = true
+db.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
@@ -1,6 +1,6 @@
use crate::{
ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack,
- StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
+ StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence,
};
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
use anyhow::{Result, anyhow};
@@ -293,35 +293,49 @@ impl DebugPanel {
);
};
- let Some(project) = self.project.upgrade() else {
- return log::error!("Debug Panel out lived it's weak reference to Project");
- };
+ let adapter_name = session.read(cx).adapter_name();
- if self
- .sessions
- .iter()
- .any(|item| item.read(cx).session_id(cx) == *session_id)
- {
- // We already have an item for this session.
- return;
- }
- let session_item = DebugSession::running(
- project,
- self.workspace.clone(),
- session,
- cx.weak_entity(),
- window,
- cx,
- );
+ let session_id = *session_id;
+ cx.spawn_in(window, async move |this, cx| {
+ let serialized_layout =
+ persistence::get_serialized_pane_layout(adapter_name).await;
- if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
- // We might want to make this an event subscription and only notify when a new thread is selected
- // This is used to filter the command menu correctly
- cx.observe(&running, |_, _, cx| cx.notify()).detach();
- }
+ this.update_in(cx, |this, window, cx| {
+ let Some(project) = this.project.upgrade() else {
+ return log::error!(
+ "Debug Panel out lived it's weak reference to Project"
+ );
+ };
- self.sessions.push(session_item.clone());
- self.activate_session(session_item, window, cx);
+ if this
+ .sessions
+ .iter()
+ .any(|item| item.read(cx).session_id(cx) == session_id)
+ {
+ // We already have an item for this session.
+ return;
+ }
+ let session_item = DebugSession::running(
+ project,
+ this.workspace.clone(),
+ session,
+ cx.weak_entity(),
+ serialized_layout,
+ window,
+ cx,
+ );
+
+ if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
+ // We might want to make this an event subscription and only notify when a new thread is selected
+ // This is used to filter the command menu correctly
+ cx.observe(&running, |_, _, cx| cx.notify()).detach();
+ }
+
+ this.sessions.push(session_item.clone());
+ this.activate_session(session_item, window, cx);
+ })
+ })
+ .detach();
}
dap_store::DapStoreEvent::RunInTerminal {
title,
@@ -13,6 +13,7 @@ use workspace::{ShutdownDebugAdapters, Workspace};
pub mod attach_modal;
pub mod debugger_panel;
mod new_session_modal;
+mod persistence;
pub(crate) mod session;
#[cfg(test)]
@@ -0,0 +1,259 @@
+use collections::HashMap;
+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, RunningState, SubView, breakpoint_list::BreakpointList, console::Console,
+ module_list::ModuleList, stack_frame_list::StackFrameList, variable_list::VariableList,
+};
+
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
+pub(crate) enum DebuggerPaneItem {
+ Console,
+ Variables,
+ BreakpointList,
+ Frames,
+ Modules,
+}
+
+impl DebuggerPaneItem {
+ 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"),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub(crate) struct SerializedAxis(pub Axis);
+
+#[derive(Debug, Serialize, Deserialize)]
+pub(crate) enum SerializedPaneLayout {
+ Pane(SerializedPane),
+ Group {
+ axis: SerializedAxis,
+ flexes: Option<Vec<f32>>,
+ children: Vec<SerializedPaneLayout>,
+ },
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub(crate) struct SerializedPane {
+ pub children: Vec<DebuggerPaneItem>,
+ pub active_item: Option<DebuggerPaneItem>,
+}
+
+pub(crate) async fn serialize_pane_layout(
+ adapter_name: SharedString,
+ pane_group: SerializedPaneLayout,
+) -> anyhow::Result<()> {
+ if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) {
+ KEY_VALUE_STORE
+ .write_kvp(
+ format!("{}-{adapter_name}", db::kvp::DEBUGGER_PANEL_PREFIX),
+ serialized_pane_group,
+ )
+ .await
+ } else {
+ Err(anyhow::anyhow!(
+ "Failed to serialize pane group with serde_json as a string"
+ ))
+ }
+}
+
+pub(crate) fn build_serialized_pane_layout(
+ pane_group: &Member,
+ cx: &mut App,
+) -> SerializedPaneLayout {
+ match pane_group {
+ Member::Axis(PaneAxis {
+ axis,
+ members,
+ flexes,
+ bounding_boxes: _,
+ }) => SerializedPaneLayout::Group {
+ axis: SerializedAxis(*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: &mut 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_pane_layout(
+ adapter_name: impl AsRef<str>,
+) -> Option<SerializedPaneLayout> {
+ let key = format!(
+ "{}-{}",
+ db::kvp::DEBUGGER_PANEL_PREFIX,
+ adapter_name.as_ref()
+ );
+
+ KEY_VALUE_STORE
+ .read_kvp(&key)
+ .log_err()
+ .flatten()
+ .and_then(|value| serde_json::from_str::<SerializedPaneLayout>(&value).ok())
+}
+
+pub(crate) fn deserialize_pane_layout(
+ serialized: SerializedPaneLayout,
+ 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>,
+ 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,
+ workspace,
+ project,
+ stack_frame_list,
+ variable_list,
+ module_list,
+ console,
+ breakpoint_list,
+ 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(
+ axis.0,
+ 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(
+ pane.focus_handle(cx),
+ stack_frame_list.clone().into(),
+ DebuggerPaneItem::Frames,
+ None,
+ cx,
+ )),
+ DebuggerPaneItem::Variables => Box::new(SubView::new(
+ variable_list.focus_handle(cx),
+ variable_list.clone().into(),
+ DebuggerPaneItem::Variables,
+ None,
+ cx,
+ )),
+ DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
+ breakpoint_list.focus_handle(cx),
+ breakpoint_list.clone().into(),
+ DebuggerPaneItem::BreakpointList,
+ None,
+ cx,
+ )),
+ DebuggerPaneItem::Modules => Box::new(SubView::new(
+ pane.focus_handle(cx),
+ module_list.clone().into(),
+ DebuggerPaneItem::Modules,
+ None,
+ cx,
+ )),
+
+ DebuggerPaneItem::Console => Box::new(SubView::new(
+ pane.focus_handle(cx),
+ console.clone().into(),
+ DebuggerPaneItem::Console,
+ Some(Box::new({
+ let console = console.clone().downgrade();
+ move |cx| {
+ console
+ .read_with(cx, |console, cx| console.show_indicator(cx))
+ .unwrap_or_default()
+ }
+ })),
+ 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()))
+ }
+ }
+}
@@ -16,6 +16,7 @@ use workspace::{
};
use crate::debugger_panel::DebugPanel;
+use crate::persistence::SerializedPaneLayout;
pub(crate) enum DebugSessionState {
Running(Entity<running::RunningState>),
@@ -52,6 +53,7 @@ impl DebugSession {
workspace: WeakEntity<Workspace>,
session: Entity<Session>,
_debug_panel: WeakEntity<DebugPanel>,
+ serialized_pane_layout: Option<SerializedPaneLayout>,
window: &mut Window,
cx: &mut App,
) -> Entity<Self> {
@@ -60,6 +62,7 @@ impl DebugSession {
session.clone(),
project.clone(),
workspace.clone(),
+ serialized_pane_layout,
window,
cx,
)
@@ -1,11 +1,13 @@
-mod breakpoint_list;
-mod console;
-mod loaded_source_list;
-mod module_list;
+pub(crate) mod breakpoint_list;
+pub(crate) mod console;
+pub(crate) mod loaded_source_list;
+pub(crate) mod module_list;
pub mod stack_frame_list;
pub mod variable_list;
-use std::{any::Any, ops::ControlFlow, sync::Arc};
+use std::{any::Any, ops::ControlFlow, sync::Arc, time::Duration};
+
+use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
use super::DebugPanelItemEvent;
use breakpoint_list::BreakpointList;
@@ -14,7 +16,7 @@ use console::Console;
use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
use gpui::{
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
- NoAction, Subscription, WeakEntity,
+ NoAction, Subscription, Task, WeakEntity,
};
use loaded_source_list::LoadedSourceList;
use module_list::ModuleList;
@@ -33,8 +35,8 @@ use ui::{
use util::ResultExt;
use variable_list::VariableList;
use workspace::{
- ActivePaneDecorator, DraggedTab, Item, Pane, PaneGroup, Workspace, item::TabContentParams,
- move_item, pane::Event,
+ ActivePaneDecorator, DraggedTab, Item, Member, Pane, PaneGroup, Workspace,
+ item::TabContentParams, move_item, pane::Event,
};
pub struct RunningState {
@@ -51,6 +53,7 @@ pub struct RunningState {
_console: Entity<Console>,
panes: PaneGroup,
pane_close_subscriptions: HashMap<EntityId, Subscription>,
+ _schedule_serialize: Option<Task<()>>,
}
impl Render for RunningState {
@@ -84,28 +87,32 @@ impl Render for RunningState {
}
}
-struct SubView {
+pub(crate) struct SubView {
inner: AnyView,
pane_focus_handle: FocusHandle,
- tab_name: SharedString,
+ kind: DebuggerPaneItem,
show_indicator: Box<dyn Fn(&App) -> bool>,
}
impl SubView {
- fn new(
+ pub(crate) fn new(
pane_focus_handle: FocusHandle,
view: AnyView,
- tab_name: SharedString,
+ kind: DebuggerPaneItem,
show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
cx: &mut App,
) -> Entity<Self> {
cx.new(|_| Self {
- tab_name,
+ kind,
inner: view,
pane_focus_handle,
show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
})
}
+
+ pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
+ self.kind
+ }
}
impl Focusable for SubView {
fn focus_handle(&self, _: &App) -> FocusHandle {
@@ -116,13 +123,19 @@ impl EventEmitter<()> for SubView {}
impl Item for SubView {
type Event = ();
+ /// This is used to serialize debugger pane layouts
+ /// A SharedString gets converted to a enum and back during serialization/deserialization.
+ fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
+ Some(self.kind.to_shared_string())
+ }
+
fn tab_content(
&self,
params: workspace::item::TabContentParams,
_: &Window,
cx: &App,
) -> AnyElement {
- let label = Label::new(self.tab_name.clone())
+ let label = Label::new(self.kind.to_shared_string())
.size(ui::LabelSize::Small)
.color(params.text_color())
.line_height_style(ui::LineHeightStyle::UiLabel);
@@ -146,7 +159,7 @@ impl Render for SubView {
}
}
-fn new_debugger_pane(
+pub(crate) fn new_debugger_pane(
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
window: &mut Window,
@@ -185,7 +198,7 @@ fn new_debugger_pane(
new_debugger_pane(workspace.clone(), project.clone(), window, cx);
let _previous_subscription = running.pane_close_subscriptions.insert(
new_pane.entity_id(),
- cx.subscribe(&new_pane, RunningState::handle_pane_event),
+ cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
);
debug_assert!(_previous_subscription.is_none());
running
@@ -354,6 +367,7 @@ impl RunningState {
session: Entity<Session>,
project: Entity<Project>,
workspace: WeakEntity<Workspace>,
+ serialized_pane_layout: Option<SerializedPaneLayout>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -382,6 +396,8 @@ impl RunningState {
)
});
+ let breakpoints = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
+
let _subscriptions = vec![
cx.observe(&module_list, |_, _, cx| cx.notify()),
cx.subscribe_in(&session, window, |this, _, event, window, cx| {
@@ -407,112 +423,40 @@ impl RunningState {
}),
];
- let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
- leftmost_pane.update(cx, |this, cx| {
- this.add_item(
- Box::new(SubView::new(
- this.focus_handle(cx),
- stack_frame_list.clone().into(),
- SharedString::new_static("Frames"),
- None,
- cx,
- )),
- true,
- false,
- None,
+ let mut pane_close_subscriptions = HashMap::default();
+ let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
+ persistence::deserialize_pane_layout(
+ serialized_layout,
+ &workspace,
+ &project,
+ &stack_frame_list,
+ &variable_list,
+ &module_list,
+ &console,
+ &breakpoints,
+ &mut pane_close_subscriptions,
window,
cx,
- );
- let breakpoints = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
- this.add_item(
- Box::new(SubView::new(
- breakpoints.focus_handle(cx),
- breakpoints.into(),
- SharedString::new_static("Breakpoints"),
- None,
- cx,
- )),
- true,
- false,
- None,
- window,
- cx,
- );
- this.activate_item(0, false, false, window, cx);
- });
- let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
- center_pane.update(cx, |this, cx| {
- this.add_item(
- Box::new(SubView::new(
- variable_list.focus_handle(cx),
- variable_list.clone().into(),
- SharedString::new_static("Variables"),
- None,
- cx,
- )),
- true,
- false,
- None,
- window,
- cx,
- );
- this.add_item(
- Box::new(SubView::new(
- this.focus_handle(cx),
- module_list.clone().into(),
- SharedString::new_static("Modules"),
- None,
- cx,
- )),
- false,
- false,
- None,
- window,
- cx,
- );
- this.activate_item(0, false, false, window, cx);
- });
- let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
- rightmost_pane.update(cx, |this, cx| {
- let weak_console = console.downgrade();
- this.add_item(
- Box::new(SubView::new(
- this.focus_handle(cx),
- console.clone().into(),
- SharedString::new_static("Console"),
- Some(Box::new(move |cx| {
- weak_console
- .read_with(cx, |console, cx| console.show_indicator(cx))
- .unwrap_or_default()
- })),
- cx,
- )),
- true,
- false,
- None,
+ )
+ }) {
+ workspace::PaneGroup::with_root(root)
+ } else {
+ pane_close_subscriptions.clear();
+ let root = Self::default_pane_layout(
+ project,
+ &workspace,
+ &stack_frame_list,
+ &variable_list,
+ &module_list,
+ &console,
+ breakpoints,
+ &mut pane_close_subscriptions,
window,
cx,
);
- });
- let pane_close_subscriptions = HashMap::from_iter(
- [&leftmost_pane, ¢er_pane, &rightmost_pane]
- .into_iter()
- .map(|entity| {
- (
- entity.entity_id(),
- cx.subscribe(entity, Self::handle_pane_event),
- )
- }),
- );
- let group_root = workspace::PaneAxis::new(
- gpui::Axis::Horizontal,
- [leftmost_pane, center_pane, rightmost_pane]
- .into_iter()
- .map(workspace::Member::Pane)
- .collect(),
- );
- let panes = PaneGroup::with_root(workspace::Member::Axis(group_root));
+ workspace::PaneGroup::with_root(root)
+ };
Self {
session,
@@ -528,21 +472,57 @@ impl RunningState {
_module_list: module_list,
_console: console,
pane_close_subscriptions,
+ _schedule_serialize: None,
+ }
+ }
+
+ fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+ if self._schedule_serialize.is_none() {
+ self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
+ cx.background_executor()
+ .timer(Duration::from_millis(100))
+ .await;
+
+ let Some((adapter_name, pane_group)) = this
+ .update(cx, |this, cx| {
+ let adapter_name = this.session.read(cx).adapter_name();
+ (
+ adapter_name,
+ persistence::build_serialized_pane_layout(&this.panes.root, cx),
+ )
+ })
+ .ok()
+ else {
+ return;
+ };
+
+ persistence::serialize_pane_layout(adapter_name, pane_group)
+ .await
+ .log_err();
+
+ this.update(cx, |this, _| {
+ this._schedule_serialize.take();
+ })
+ .ok();
+ }));
}
}
- fn handle_pane_event(
+ pub(crate) fn handle_pane_event(
this: &mut RunningState,
- source_pane: Entity<Pane>,
+ source_pane: &Entity<Pane>,
event: &Event,
+ window: &mut Window,
cx: &mut Context<RunningState>,
) {
+ this.serialize_layout(window, cx);
if let Event::Remove { .. } = event {
let _did_find_pane = this.panes.remove(&source_pane).is_ok();
debug_assert!(_did_find_pane);
cx.notify();
}
}
+
pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
if self.thread_id.is_some() {
self.stack_frame_list
@@ -586,7 +566,7 @@ impl RunningState {
.find_map(|pane| {
pane.read(cx)
.items_of_type::<SubView>()
- .position(|view| view.read(cx).tab_name == *"Modules")
+ .position(|view| view.read(cx).view_kind().to_shared_string() == *"Modules")
.map(|view| (view, pane))
})
.unwrap();
@@ -802,6 +782,127 @@ impl RunningState {
}),
)
}
+
+ fn default_pane_layout(
+ project: Entity<Project>,
+ workspace: &WeakEntity<Workspace>,
+ stack_frame_list: &Entity<StackFrameList>,
+ variable_list: &Entity<VariableList>,
+ module_list: &Entity<ModuleList>,
+ console: &Entity<Console>,
+ breakpoints: Entity<BreakpointList>,
+ subscriptions: &mut HashMap<EntityId, Subscription>,
+ window: &mut Window,
+ cx: &mut Context<'_, RunningState>,
+ ) -> Member {
+ let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
+ leftmost_pane.update(cx, |this, cx| {
+ this.add_item(
+ Box::new(SubView::new(
+ this.focus_handle(cx),
+ stack_frame_list.clone().into(),
+ DebuggerPaneItem::Frames,
+ None,
+ cx,
+ )),
+ true,
+ false,
+ None,
+ window,
+ cx,
+ );
+ this.add_item(
+ Box::new(SubView::new(
+ breakpoints.focus_handle(cx),
+ breakpoints.into(),
+ DebuggerPaneItem::BreakpointList,
+ None,
+ cx,
+ )),
+ true,
+ false,
+ None,
+ window,
+ cx,
+ );
+ this.activate_item(0, false, false, window, cx);
+ });
+ let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
+ center_pane.update(cx, |this, cx| {
+ this.add_item(
+ Box::new(SubView::new(
+ variable_list.focus_handle(cx),
+ variable_list.clone().into(),
+ DebuggerPaneItem::Variables,
+ None,
+ cx,
+ )),
+ true,
+ false,
+ None,
+ window,
+ cx,
+ );
+ this.add_item(
+ Box::new(SubView::new(
+ this.focus_handle(cx),
+ module_list.clone().into(),
+ DebuggerPaneItem::Modules,
+ None,
+ cx,
+ )),
+ false,
+ false,
+ None,
+ window,
+ cx,
+ );
+ this.activate_item(0, false, false, window, cx);
+ });
+ let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
+ rightmost_pane.update(cx, |this, cx| {
+ let weak_console = console.downgrade();
+ this.add_item(
+ Box::new(SubView::new(
+ this.focus_handle(cx),
+ console.clone().into(),
+ DebuggerPaneItem::Console,
+ Some(Box::new(move |cx| {
+ weak_console
+ .read_with(cx, |console, cx| console.show_indicator(cx))
+ .unwrap_or_default()
+ })),
+ cx,
+ )),
+ true,
+ false,
+ None,
+ window,
+ cx,
+ );
+ });
+
+ subscriptions.extend(
+ [&leftmost_pane, ¢er_pane, &rightmost_pane]
+ .into_iter()
+ .map(|entity| {
+ (
+ entity.entity_id(),
+ cx.subscribe_in(entity, window, Self::handle_pane_event),
+ )
+ }),
+ );
+
+ let group_root = workspace::PaneAxis::new(
+ gpui::Axis::Horizontal,
+ [leftmost_pane, center_pane, rightmost_pane]
+ .into_iter()
+ .map(workspace::Member::Pane)
+ .collect(),
+ );
+
+ Member::Axis(group_root)
+ }
}
impl EventEmitter<DebugPanelItemEvent> for RunningState {}
@@ -27,7 +27,7 @@ use ui::{
use util::{ResultExt, maybe};
use workspace::Workspace;
-pub(super) struct BreakpointList {
+pub(crate) struct BreakpointList {
workspace: WeakEntity<Workspace>,
breakpoint_store: Entity<BreakpointStore>,
worktree_store: Entity<WorktreeStore>,
@@ -68,6 +68,7 @@ pub async fn init_test_workspace(
workspace_handle
}
+#[track_caller]
pub fn active_debug_session_panel(
workspace: WindowHandle<Workspace>,
cx: &mut TestAppContext,
@@ -81,6 +81,8 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
})
.await;
+ cx.run_until_parked();
+
// assert we have a debug panel item before the session has stopped
workspace
.update(cx, |workspace, _window, cx| {
@@ -229,6 +231,8 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
})
.await;
+ cx.run_until_parked();
+
// assert we have a debug panel item before the session has stopped
workspace
.update(cx, |workspace, _window, cx| {
@@ -1052,6 +1056,8 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
}))
.await;
+ cx.run_until_parked();
+
let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| {
item.mode()
.as_running()
@@ -1538,6 +1538,8 @@ async fn test_variable_list_only_sends_requests_when_rendering(
})
.await;
+ cx.run_until_parked();
+
let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| {
let state = item
.mode()
@@ -30,7 +30,8 @@ use dap::{
use futures::channel::oneshot;
use futures::{FutureExt, future::Shared};
use gpui::{
- App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task, WeakEntity,
+ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,
+ Task, WeakEntity,
};
use rpc::AnyProtoClient;
use serde_json::{Value, json};
@@ -125,6 +126,7 @@ type UpstreamProjectId = u64;
struct RemoteConnection {
_client: AnyProtoClient,
_upstream_project_id: UpstreamProjectId,
+ _adapter_name: SharedString,
}
impl RemoteConnection {
@@ -996,6 +998,7 @@ impl Session {
) -> Self {
Self {
mode: Mode::Remote(RemoteConnection {
+ _adapter_name: SharedString::new(""), // todo(debugger) we need to pipe in the right values to deserialize the debugger pane layout
_client: client,
_upstream_project_id: upstream_project_id,
}),
@@ -1044,6 +1047,13 @@ impl Session {
&self.capabilities
}
+ pub fn adapter_name(&self) -> SharedString {
+ match &self.mode {
+ Mode::Local(local_mode) => local_mode.adapter.name().into(),
+ Mode::Remote(remote_mode) => remote_mode._adapter_name.clone(),
+ }
+ }
+
pub fn configuration(&self) -> Option<DebugAdapterConfig> {
if let Mode::Local(local_mode) = &self.mode {
Some(local_mode.config.clone())