debugger: Remember pane layout from previous debugger session (#28692)

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>
This commit is contained in:
Anthony Eid 2025-04-15 02:32:28 -04:00 committed by GitHub
parent b794919842
commit d4761cea47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 550 additions and 150 deletions

1
Cargo.lock generated
View file

@ -4177,6 +4177,7 @@ dependencies = [
"collections",
"command_palette_hooks",
"dap",
"db",
"editor",
"env_logger 0.11.8",
"feature_flags",

View file

@ -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!(

View file

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

View file

@ -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,

View file

@ -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)]

View file

@ -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()))
}
}
}

View file

@ -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,
)

View file

@ -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,
)
}) {
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 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,
window,
cx,
);
});
let pane_close_subscriptions = HashMap::from_iter(
[&leftmost_pane, &center_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 handle_pane_event(
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();
}));
}
}
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, &center_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 {}

View file

@ -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>,

View file

@ -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,

View file

@ -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()

View file

@ -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()

View file

@ -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())